Skip to content

Commit

Permalink
Merge pull request #567 from gristow/stavenote-styling
Browse files Browse the repository at this point in the history
Styling via Element class; add glyph & beam styling; add stem, flag and ledger line styling to StaveNotes; fixes #557
  • Loading branch information
gristow committed Jul 28, 2017
2 parents 90cd4da + aea381e commit 9f6f1b2
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 34 deletions.
3 changes: 3 additions & 0 deletions src/beam.js
Expand Up @@ -825,7 +825,10 @@ export class Beam extends Element {
if (!this.postFormatted) {
this.postFormat();
}

this.drawStems();
this.applyStyle();
this.drawBeamLines();
this.restoreStyle();
}
}
23 changes: 23 additions & 0 deletions src/element.js
Expand Up @@ -30,6 +30,29 @@ export class Element {
}
}

// set the draw style of a stemmable note:
setStyle(style) { this.style = style; return this; }
getStyle() { return this.style; }

// Apply current style to Canvas `context`
applyStyle(context = this.context, style = this.getStyle()) {
if (!style) return this;

context.save();
if (style.shadowColor) context.setShadowColor(style.shadowColor);
if (style.shadowBlur) context.setShadowBlur(style.shadowBlur);
if (style.fillStyle) context.setFillStyle(style.fillStyle);
if (style.strokeStyle) context.setStrokeStyle(style.strokeStyle);

return this;
}

restoreStyle(context = this.context, style = this.getStyle()) {
if (!style) return this;
context.restore();
return this;
}

// An element can have multiple class labels.
hasClass(className) { return (this.attrs.classes[className] === true); }
addClass(className) {
Expand Down
4 changes: 4 additions & 0 deletions src/glyph.js
Expand Up @@ -219,7 +219,9 @@ export class Glyph extends Element {
const scale = this.scale;

this.setRendered();
this.applyStyle(ctx);
Glyph.renderOutline(ctx, outline, scale, x + this.originShift.x, y + this.originShift.y);
this.restoreStyle(ctx);
}

renderToStave(x) {
Expand All @@ -237,7 +239,9 @@ export class Glyph extends Element {
const scale = this.scale;

this.setRendered();
this.applyStyle();
Glyph.renderOutline(this.context, outline, scale,
x + this.x_shift, this.stave.getYForGlyphs() + this.y_shift);
this.restoreStyle();
}
}
20 changes: 1 addition & 19 deletions src/notehead.js
Expand Up @@ -118,13 +118,6 @@ export class NoteHead extends Note {
// Determine if the notehead is displaced
isDisplaced() { return this.displaced === true; }

// Get/set the notehead's style
//
// `style` is an `object` with the following properties: `shadowColor`,
// `shadowBlur`, `fillStyle`, `strokeStyle`
getStyle() { return this.style; }
setStyle(style) { this.style = style; return this; }

// Get the glyph data
getGlyph() { return this.glyph; }

Expand Down Expand Up @@ -169,16 +162,6 @@ export class NoteHead extends Note {
return new Flow.BoundingBox(this.getAbsoluteX(), min_y, this.width, spacing);
}

// Apply current style to Canvas `context`
applyStyle(context) {
const style = this.getStyle();
if (style.shadowColor) context.setShadowColor(style.shadowColor);
if (style.shadowBlur) context.setShadowBlur(style.shadowBlur);
if (style.fillStyle) context.setFillStyle(style.fillStyle);
if (style.strokeStyle) context.setStrokeStyle(style.strokeStyle);
return this;
}

// Set notehead to a provided `stave`
setStave(stave) {
const line = this.getLine();
Expand Down Expand Up @@ -241,10 +224,9 @@ export class NoteHead extends Note {
drawSlashNoteHead(ctx, this.duration, head_x, y, stem_direction, staveSpace);
} else {
if (this.style) {
ctx.save();
this.applyStyle(ctx);
Glyph.renderGlyph(ctx, head_x, y, glyph_font_scale, this.glyph_code);
ctx.restore();
this.restoreStyle(ctx);
} else {
Glyph.renderGlyph(ctx, head_x, y, glyph_font_scale, this.glyph_code);
}
Expand Down
28 changes: 28 additions & 0 deletions src/stavenote.js
Expand Up @@ -350,7 +350,12 @@ export class StaveNote extends StemmableNote {

reset() {
super.reset();

// Save prior noteHead styles & reapply them after making new noteheads.
const noteHeadStyles = this.note_heads.map(noteHead => noteHead.getStyle());
this.buildNoteHeads();
this.note_heads.forEach((noteHead, index) => noteHead.setStyle(noteHeadStyles[index]));

if (this.stave) {
this.note_heads.forEach(head => head.setStave(this.stave));
}
Expand Down Expand Up @@ -723,10 +728,23 @@ export class StaveNote extends StemmableNote {
// Sets the style of the complete StaveNote, including all keys
// and the stem.
setStyle(style) {
super.setStyle(style);
this.note_heads.forEach(notehead => notehead.setStyle(style));
this.stem.setStyle(style);
}

setStemStyle(style) {
const stem = this.getStem();
stem.setStyle(style);
}
getStemStyle() { return this.stem.getStyle(); }

setLedgerLineStyle(style) { this.ledgerLineStyle = style; }
getLedgerLineStyle() { return this.ledgerLineStyle; }

setFlagStyle(style) { this.flagStyle = style; }
getFlagStyle() { return this.flagStyle; }

// Sets the notehead at `index` to the provided coloring `style`.
//
// `style` is an `object` with the following properties: `shadowColor`,
Expand Down Expand Up @@ -923,13 +941,15 @@ export class StaveNote extends StemmableNote {
ctx.fillRect(x, y, length, 1);
};

this.applyStyle(ctx, this.getLedgerLineStyle() || false);
for (let line = 6; line <= highest_line; ++line) {
drawLedgerLine(stave.getYForNote(line));
}

for (let line = 0; line >= lowest_line; --line) {
drawLedgerLine(stave.getYForNote(line));
}
this.restoreStyle(ctx, this.getLedgerLineStyle() || false);
}

// Draw all key modifiers
Expand Down Expand Up @@ -981,7 +1001,9 @@ export class StaveNote extends StemmableNote {

// Draw the Flag
ctx.openGroup('flag', null, { pointerBBox: true });
this.applyStyle(ctx, this.getFlagStyle() || false);
this.flag.render(ctx, flagX, flagY);
this.restoreStyle(ctx, this.getFlagStyle() || false);
ctx.closeGroup();
}
}
Expand All @@ -997,6 +1019,9 @@ export class StaveNote extends StemmableNote {

// Render the stem onto the canvas
drawStem(stemStruct) {
// GCR TODO: I can't find any context in which this is called with the stemStruct
// argument in the codebase or tests. Nor can I find a case where super.drawStem
// is called at all. Perhaps these should be removed?
if (!this.context) {
throw new Vex.RERR('NoCanvasContext', "Can't draw without a canvas context.");
}
Expand Down Expand Up @@ -1037,6 +1062,8 @@ export class StaveNote extends StemmableNote {
// Draw each part of the note
this.drawLedgerLines();

// Apply the overall style -- may be contradicted by local settings:
this.applyStyle();
this.setAttribute('el', this.context.openGroup('stavenote', this.getAttribute('id')));
this.context.openGroup('note', null, { pointerBBox: true });
if (shouldRenderStem) this.drawStem();
Expand All @@ -1045,6 +1072,7 @@ export class StaveNote extends StemmableNote {
this.context.closeGroup();
this.drawModifiers();
this.context.closeGroup();
this.restoreStyle();
this.setRendered();
}
}
15 changes: 0 additions & 15 deletions src/stem.js
Expand Up @@ -104,10 +104,6 @@ export class Stem extends Element {
return { topY: stemTipY, baseY: outerMostNoteheadY };
}

// set the draw style of a stem:
setStyle(style) { this.style = style; return this; }
getStyle() { return this.style; }

setVisibility(isVisible) {
this.hide = !isVisible;
return this;
Expand All @@ -119,17 +115,6 @@ export class Stem extends Element {
return this;
}

// Apply current style to Canvas `context`
applyStyle(context) {
const style = this.getStyle();
if (style) {
if (style.shadowColor) context.setShadowColor(style.shadowColor);
if (style.shadowBlur) context.setShadowBlur(style.shadowBlur);
if (style.strokeStyle) context.setStrokeStyle(style.strokeStyle);
}
return this;
}

// Render the stem onto the canvas
draw() {
this.setRendered();
Expand Down
99 changes: 99 additions & 0 deletions tests/stavenote_tests.js
Expand Up @@ -31,6 +31,9 @@ VF.Test.StaveNote = (function() {
runTests('StaveNote Draw - Bass 2', StaveNote.drawBass);
runTests('StaveNote Draw - Key Styles', StaveNote.drawKeyStyles);
runTests('StaveNote Draw - StaveNote Styles', StaveNote.drawNoteStyles);
runTests('StaveNote Draw - StaveNote Stem Styles', StaveNote.drawNoteStemStyles);
runTests('StaveNote Draw - StaveNote Flag Styles', StaveNote.drawNoteStylesWithFlag);
runTests('StaveNote Draw - Beam, Stem & Ledger Line Styles', StaveNote.drawBeamStyles);
runTests('Flag and Dot Placement - Stem Up', StaveNote.dotsAndFlagsStemUp);
runTests('Flag and Dots Placement - Stem Down', StaveNote.dotsAndFlagsStemDown);
runTests('Beam and Dot Placement - Stem Up', StaveNote.dotsAndBeamsUp);
Expand Down Expand Up @@ -599,6 +602,102 @@ VF.Test.StaveNote = (function() {
ok(note.getYs().length > 0, 'Note has Y values');
},

drawNoteStemStyles: function(options, contextBuilder) {
var ctx = new contextBuilder(options.elementId, 300, 280);
var stave = new VF.Stave(10, 0, 100);
ctx.scale(3, 3);

var note = new VF.StaveNote({ keys: ['g/4', 'bb/4', 'd/5'], duration: 'q' })
.setStave(stave)
.addAccidental(1, new VF.Accidental('b'));

note.setStemStyle({ shadowBlur: 15, shadowColor: 'blue', fillStyle: 'blue', strokeStyle: 'blue' });

new VF.TickContext()
.addTickable(note)
.preFormat()
.setX(25);

stave.setContext(ctx).draw();
note.setContext(ctx).draw();

ok('Note Stem Style');
},

drawNoteStylesWithFlag: function(options, contextBuilder) {
var ctx = new contextBuilder(options.elementId, 300, 280);
var stave = new VF.Stave(10, 0, 100);
ctx.scale(3, 3);

var note = new VF.StaveNote({ keys: ['g/4', 'bb/4', 'd/5'], duration: '8' })
.setStave(stave)
.addAccidental(1, new VF.Accidental('b'));

note.setStyle({ shadowBlur: 15, shadowColor: 'blue', fillStyle: 'blue', strokeStyle: 'blue' });

new VF.TickContext()
.addTickable(note)
.preFormat()
.setX(25);

stave.setContext(ctx).draw();
note.setContext(ctx).draw();

ok(note.getX() > 0, 'Note has X value');
ok(note.getYs().length > 0, 'Note has Y values');
},

drawBeamStyles: function(options, contextBuilder) {
var ctx = new contextBuilder(options.elementId, 200, 180);
var stave = new VF.Stave(10, 10, 180);
stave.setContext(ctx);
stave.draw();

var notes = [
// Beam
{ keys: ['b/4'], duration: '8', stem_direction: -1 },
{ keys: ['b/4'], duration: '8', stem_direction: -1 },
{ keys: ['b/4'], duration: '8', stem_direction: 1 },
{ keys: ['b/4'], duration: '8', stem_direction: 1 },
{ keys: ['d/6'], duration: '8', stem_direction: -1 },
{ keys: ['c/6', 'd/6'], duration: '8', stem_direction: -1 },
{ keys: ['d/6', 'e/6'], duration: '8', stem_direction: -1 },
];

var stave_notes = notes.map(function(note) { return new VF.StaveNote(note); });
stave_notes[0].setStemStyle({ strokeStyle: 'green' });
stave_notes[1].setStemStyle({ strokeStyle: 'orange' });

stave_notes[0].setKeyStyle(0, { fillStyle: 'purple' });
stave_notes[4].setLedgerLineStyle({ fillStyle: 'red', strokeStyle: 'red' });

var beam1 = new VF.Beam([stave_notes[0], stave_notes[1]]);
var beam2 = new VF.Beam([stave_notes[2], stave_notes[3]]);
var beam3 = new VF.Beam(stave_notes.slice(4, 6));

stave_notes[1].setKeyStyle(0, { fillStyle: 'chartreuse' });
stave_notes[2].setStyle({ fillStyle: 'tomato', strokeStyle: 'tomato' });

stave_notes[6].setFlagStyle({ fillStyle: 'orange', strokeStyle: 'orante' });

beam1.setStyle({
fillStyle: 'blue',
strokeStyle: 'blue',
});

beam2.setStyle({
shadowBlur: 20,
shadowColor: 'blue',
});

VF.Formatter.FormatAndDraw(ctx, stave, stave_notes, false);

beam1.setContext(ctx).draw();
beam2.setContext(ctx).draw();
beam3.setContext(ctx).draw();

ok('draw beam styles');
},

renderNote: function(note, stave, ctx, x) {
note.setStave(stave);
Expand Down

0 comments on commit 9f6f1b2

Please sign in to comment.