Vexflow: Styling modifiers, staveModifiers, glyphs

Created on 25 Mar 2015  路  5Comments  路  Source: 0xfe/vexflow

In working on a GUI project, I'm finding the need to color & style modifiers & specific glyphs on a pretty regular basis. For instance, when a user clicks on a barline, I'd like to be able to color it to show it as selected.

There's some consistency in how we do this within StaveNote & NoteHead, using the setStyle() functions -- my suggestion is to expand this to implement setStyle() within modifiers, staveModifiers and glyphs so that specific styling can be set at any level.

If this is something others would be interested in, I'm happy to implement & send a PR.

discussion

Most helpful comment

Registering elements is now easy to do: we have the base class, Element, for all renderable elements, and we have the Factory class. All elements also now have attributes (like id and type) and corresponding setAttribute and getAttribute calls.

The next step is to create a simple Registry class that all elements generated by Factory auto register with. Once this is done, it would be really simple to use CSS to change the attributes of elements in a registry. The hard (and fun) part would be to actually decide on what attributes we need for each element and implement them.

All 5 comments

Yes, interested. I'd like to have some consistency in the styling of all the elements.

Really, the end goal (as I see it) is to support out-of-band styling via stylesheets, which will significantly cut down the code required to style scores. To do this we need:

  • an internal style object (key-value map) for each element (it could even have styles like "xShift", "yShift", etc.)
  • setStyle(): which sets these styles.
  • A style-aware draw, which applies the styles as they are rendered.
  • internal id and class fields, similar to CSS.
  • a global StyleManager class, to which all elements are auto-registered when they are created, to allow for applying styles based on id and class.
  • a stylesheet format and parser which uses StyleManager to apply styles in a stylesheet.

I think your adding a consistent setStyle aligns with this larger goal. So please go ahead and implement if you're still interested, but keep the larger goal in mind as you design the API. Thanks.

(FYI, I'm still out on vacation with spotty Internet, and will be slow to respond.)

Yes, I agree, CSS-like styling is the way to go. Having a style object on each class will allow us to interact with these objects just as we would applying styles to DOM objects in ECMAScript. We can also implement something similar to Element.classlist for adding & removing classes to an object to keep the script as similar as possible to working directly with DOM objects.

Within SVGContext we can implement it as actual CSS, with the option to write inline CSS to the SVG object or refer to an external stylesheet. This will clean up our SVG objects significantly (we should see a rendering speed boost), and will give those using SVGContext access to SVG elements programmatically with getElementById. We can allow users to access to the VexFlow objects in a similar way via StyleManager.

Assuming we use as a starting point SVG's support for CSS, we'll want to aim to support these properties (many of which are supported in Canvas but not yet implemented in canvasContext):
*fill -- already implemented
*fillOpacity
*fillRule
*marker
*markerStart
*markerMid
*markerEnd
*stroke -- already implemented
*strokeDasharray
*strokeDashoffset
*strokeLinecap -- already implemented
*strokeMiterlimit
*strokeOpacity
*strokeWidth -- already implemented

For shadowColor and shadowBlur which don't have native SVG support, we'll need to continue to implement these internally or create SVG filter definitions to mimic their behavior.

If it sounds like we're on the same page, I'll go ahead and get started on this. The SVG implementation will be quite easy, really. I'll build this on a few frequently used types first staveNote, noteHead, stem, and stave, and we can see if it's going in a direction we like. Should have this done in about a week.

Greg

I am wrestling with three possible architectures in implementing the StyleManager -- though otherwise making very good progress.

Any thoughts on which of these is best? (I'm leaning towards the third.)

  1. All elements will be auto-registered to StyleManger on creation. To facilitate that, we initialize an instance of StyleManager at something like Vex.Flow.styleManager whenever vexflow is loaded, so elements can reach it without being passed a reference. This has not been our pattern with other objects, but makes sense to me because StyleManager should be a somewhat 'global' entity.

But, it raises the issue of garbage collection: StyleManager would retain a reference to every element created. (In a project I'm working on, for instance, I use vexflow to render music from another format -- and re-create all vexflow elements at each redraw, rather than updating the individual vexflow elements. In this model, I'd need to be sure I cleared the StyleManager at each redraw.)

  1. In the DOM, where objects are added to document, or a child like body or a div, there's no need to register with a StyleManger because styles & classes are applied by iterating over objects in the document tree. We could go this route -- but this would be a _major_ change. Instead of directly calling element.draw() we'd call something like context.add(element) and eventually context.draw() which would traverse its child elements for style, class & id definitions. If we ever plan to deprecate canvas rendering in favor of SVG rendering, I'd recommend this design, as our internal structure would be more closely aligned with the SVG structure and we could even eventually bind changes on vexflow objects to immediate changes in the SVG without a full context.clear() and redrawing of every element.
  2. Or we could simply not use a StyleManager -- although it would eliminate the possibility of using something like StyleManager.getElementById() or StyleManager.getElementsByClassName(). These may not be needed, since unlike the DOM, all elements are created in JavaScript anyway. We would instead register a StyleSheets object to the context. When each element.draw() was called, we would consult the element's style object and apply styles by consulting the context.styleSheets object as needed.

Thoughts? Am I missing another better way of implementing this?

Greg

I think 1) is the best approach. I don't want to rely on the DOM to keep track of the elements -- that would couple it too tightly, and cause headaches in the future.

Here's one strategy for this:

1) Require StyleManager auto-registration to be explicitly enabled. So, if users don't enable it, no elements are tracked and don't need to explicitly clear stuff.
2) Have some way to specify a "group name" for all following new elements. E.g., StyleManager.SetGroup('measure4'). All new elements after this call fall in group "measure4". If no group name is provided (or if SetGroup is not called), all elements simply fall into a "global" group.
3) Provide a clear(groupname) method which the user calls everytime they want to GC elements of that group. No parameter means clear everything (or "global").

The goal here is to have an API that makes it explicit that the user needs to call "clear()" to GC unused elements. This is only necessary if they manually enable auto-registration.

The grouping is a simple optimization for (what I suspect will be) the common case, where users just redraw single measures or subsections of a score, as opposed to the whole score.

How does this sound?

Registering elements is now easy to do: we have the base class, Element, for all renderable elements, and we have the Factory class. All elements also now have attributes (like id and type) and corresponding setAttribute and getAttribute calls.

The next step is to create a simple Registry class that all elements generated by Factory auto register with. Once this is done, it would be really simple to use CSS to change the attributes of elements in a registry. The hard (and fun) part would be to actually decide on what attributes we need for each element and implement them.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

graciano picture graciano  路  4Comments

HollyJHuber picture HollyJHuber  路  5Comments

Silverwolf90 picture Silverwolf90  路  4Comments

PCrompton picture PCrompton  路  3Comments

erquiaga picture erquiaga  路  7Comments