Closed kasper573 closed 9 years ago
Yes, it is possible to use GSS without DOM. In fact, that's one of the main things we were trying to accomplish with two rewrites we've done.
GSS has domain abstraction, each domain is a combination of solver and commands that it understands. It's built in a way that solver may only understand a part of expression, and then pass it back to the pipeline for somebody else to finish (usually, it's cassowary in linear domain).
When you work with DOM, you use two domains specific to HTML:
1) Document, which is an instance of Abstract domain, that parses stylesheets, observes selectors and outputs commands to linear solver. https://github.com/gss/engine/blob/types/src/domains/Document.coffee
Abstract domain itself is enough to handle GSS constraints and conditions, for example worker computations don't use Document, but rather Abstract directly. You can use abstract or subclass it.
2) Intrinsic domain, which provides measurements for GSS to use in expressions. A numeric domain, which is not a solver in a sense that it does not produce final values, but rather intermediate values for variables, and it can execute simple algebraic expressions to be used as substitutions in linear expressions.
There's a convention that if you use a property with a prefix #el[intrinsic-width]
and there is intrinsic
domain defined, it'll be used to compute the property.
https://github.com/gss/engine/blob/types/src/domains/Intrinsic.coffee
Since pure WebGL context doesn't use text, you will have to provide your own logic that will measure elements. But it's not a big deal, unless you want to implement reflows if some conten't doesn't fit the provided space. If you do, then GSS expects that you can compute values before equasions hit linear solver, even if you just output zeros for every measurement initially. After gss thinks it solved the layout, you can validate it and cause a reflow by emitting updates to measurements.
Intrinsic domain does its computations in batch in perform() method. If your measurements are pure and do not need to be processed in batches, you can simply define your own property handlers https://github.com/gss/engine/blob/types/src/properties/Dimensions.coffee
In the linked file you can find scroll-top/left and window[width]/height can be accessed, computed and updated out of batch. Here's how we invalidate multiple properties in transaction: https://github.com/gss/engine/blob/types/src/domains/Document.coffee#L66
Somewhat more direct approach, would be to use assumed values. It's a domain that provides and updates values for other solvers. GSS accepts them in constructor and solve method as a shortcut.
engine = new GSS({md: 72})
engine.solve(['==', ['get', 'md'], ['get', 'a']]) //a: 72. md:72
engine.solve({md: 36}) //a: 36. md: 36
Numeric measurements and assumed values have the top-most priority in cassowary. You can do that to variables that previously were computed linearily. GSS will replace original constraint and recompute values.
engine = new GSS
engine.solve(['==', ['get', 'md'], ['get', 'a']], ['==', ['get', 'md', 72]]) //a: 72. md:72
engine.solve({md: 36}) //a: 36. md: 36
3) GSS fires 'apply', 'write' and 'solve' events on the latestst of queries branch. The difference is, 'solve' happens only once, while 'apply' and 'write' can cause side effects (measured by intrinsic domain) and cause another reflow. Solutions contain both global variables and computed element properties in {"$el[width]": 10}
format. Parse the key string to apply values to the outside objects of your desire.
I'll be happy to guide you through to extend gss to suit your needs. We're interested in positive integrations, and we're planning to outline all the customization points in docs in future. Good luck!
Thanks for the quick and detailed response, interesting stuff!
Let's be transparent: I'm using React, wrapping Pixi.
Context: This is an experimental project to try to find a powerful and expressive way to build Immediate Mode GUI in an interpreted language. Javascript is my preference, React provides the strength of components while Pixi provides a simple interface to unlock the power of the GPU, which is vital in the project this experiment is set out to help (just to clear out any questions of why I'm not using the DOM).
What's missing is styles and layouting. At this point I'm only trying to solve layouting, and this is what I would like to extract from GSS (but I'm open to more flexible solutions, of course).
So if we tackle this problem from a constraints perspective (hehe), we see that we have React on one end and WebGL (Pixi) on the other. In my mind, the only logical place for us to put layouting is right in the middle. React components update and render, and part of their render algorithm is the GSS layouting.
Here's my crazy idea:
var Content = React.createClass({
render: function () {
// AutoLayout is a simple component that passes its children
// though a magical layouting function on mount and update.
return (
<AutoLayout>
<CustomComponent data="Foo"/>
<TilingSprite image="myimage.png"/>
<Text text="Baz"/>
</AutoLayout>
);
}
});
Here is the layouting function. A simple proxy of an imaginary layouting function that I hope gss exposes in some way.
function autoLayout (reactElements, gss) {
// Convert the react elements to objects that the gss layout engine understands
var layoutElements = layoutElementsFromReactElements(reactElements);
// Use the imaginary function gssLayout to solve all our problems.
// It returns the layout elements with styles applied.
var styledLayoutElements = gssLayout(layoutElements, gss);
// Use the GSS layout result to create modified versions of the original react elements
return reactElementsFromLayoutElements(styledLayoutElements);
}
function layoutElementsFromReactElements (reactElements) {
return reactElements.map(function (element) {
// NOTE we probably need to transform/rename depending on the gss interface.
return {
id: element.props.id,
x: element.props.x,
y: element.props.y,
width: element.props.width,
height: element.props.height,
// A meta property would be useful so we can remember the element
meta: element
};
});
}
function reactElementsFromLayoutElements (layoutElements) {
return layoutElements.map(function (element) {
return React.addons.cloneWithProps(element.meta, {
// Apply new bounds
y: element.y,
x: element.x,
width: element.width,
height: element.height
});
});
}
What do you think?
@Kasu are you aware of https://github.com/petehunt/react-gss and React: CSS in JS?
Neither could be applied as-is to your project but I think you'll find the concepts useful.
I think this could work, but it feels way too manual. In gss you can define your own type of document, add selector support to it, and provide your own logic to apply styles. The question is, if you want your UI to be purely virtual (immediate) or have some object representation and be mutable in react (more like DOM). In case of immediate UI's your goal is to fire and forget and GSS can work it out pretty well.
GSS is actually a pretty good choice for the task, as it doesnt build or keep intermediate objects that represent reactangles and stuff. It just operates on Queries, Variables and Constraints. If your queries produce non-stale data, gss does the rest properly.
When dealing with dynamically generated constraints, it's a common situation where you'd like to remove some constraints and it's easy to do:
engine.solve([['get', 'a'], 10], 'my-tracker'] //add constraint, a: 10 engine.remove(['remove', 'my-tracker']) //remove constraint, a: null
I recommend you to take a look at GSS's parsed output. It produces pretty clean AST that you could write by hand to be used as constraints in your imaginary gssLayout funciton. react-gss didnt make a good use of it, because it was built for the older version, where ast was not very expressive
I'd like to use the complete GSS or part of it without rendering in the DOM. Specifically, I'm using WebGL in my app, but I'm still in need lf layouting. GSS looks promising, but I have no idea how to utilize it with WebGL, or without the DOM at all actually.
Any ideas on how to do this?