Skip to content
Browse files

smash merge from postbooks work

- added global log of storeKey changes, plus the ability to capture .get()s on records during a given interval
- added Init, Enter, and Exit events
- added SC.renderToDataUrl() function
- added tooltip support to SC.Responder
- behaviors are now Hierarchical State Machines
- bug fix for row layer tree layout: force width/height
- clean up SC.Responder
- commented out a log statement
- elide() strings that are too long for the text field
- field editors
- first pass at shared canvas contexts
- first stab at SC.TableView
- fixed text editing bug on mouseDown (would not clear selection)
- get layer in lists working
- got mouse events working correctly with incremental rendering
- got render functions on list items working again
- got text fields working in editable rows
- implement dividers in SC.SelectWidget
- implemented behavior system
- implemented SC.PListItem handling
- improved field editor protocol
- make SC.Responder a mixin
- more shift-tab/tab handling
- more styling improvements
- moved constraints framework outside application to work around error in xtuple buildtools
- removed old-style behaviors
- some scrollbar fixes
- styled checkbox
- styled combobox and text field widgets
- styled radio button
- styled select widget
- tab/shift-tab handling
- various tweaks
- verify that context menus work on layer-backed list item rows
- WIP incremental list view
  • Loading branch information...
1 parent 0fa8fbe commit 42d23f462cd8fc571a6c1d4bc98e5659049a3ff0 @erichocean erichocean committed
Showing with 4,019 additions and 1,631 deletions.
  1. +23 −20 application/behaviors/behavior.js
  2. +0 −109 application/behaviors/generic_button.js
  3. +0 −74 application/behaviors/mouse_button.js
  4. +0 −65 application/behaviors/touch_button.js
  5. +1 −1 application/core.js
  6. +1 −12 application/ext/browser.js
  7. +10 −0 application/ext/run_loop.js
  8. +4 −7 application/layers/label.js
  9. +57 −22 application/layers/layer.js
  10. +11 −11 application/layers/text.js
  11. +23 −0 application/mixins/string.js
  12. +1 −1 application/node/buildfile.js
  13. +974 −0 application/surfaces/ilist_view.js
  14. +10 −2 application/surfaces/list_view.js
  15. +14 −9 application/surfaces/private/psurface.js
  16. +19 −2 application/surfaces/scroll_view.js
  17. +1 −2 application/surfaces/split.js
  18. +5 −1 application/surfaces/surface.js
  19. +381 −0 application/surfaces/table_view.js
  20. +64 −25 application/surfaces/view.js
  21. +136 −36 application/system/application.js
  22. +40 −0 application/system/data_url.js
  23. +23 −0 application/system/event.js
  24. +634 −31 application/system/responder.js
  25. +125 −73 application/text/field_editor.js
  26. +12 −43 application/widgets/button.js
  27. +42 −35 application/widgets/checkbox.js
  28. +23 −58 application/widgets/combobox.js
  29. +42 −38 application/widgets/radio.js
  30. +19 −22 application/widgets/segmented.js
  31. +118 −103 application/widgets/select.js
  32. +134 −109 application/widgets/text_field.js
  33. +55 −92 application/widgets/widget.js
  34. +8 −1 buildtools/buildtools.js
  35. 0 {application → }/constraints/README.md
  36. 0 {application → }/constraints/constraint.js
  37. 0 {application → }/constraints/core.js
  38. 0 {application → }/constraints/edit_info.js
  39. 0 {application → }/constraints/error.js
  40. 0 {application → }/constraints/hash_set.js
  41. 0 {application → }/constraints/hash_table.js
  42. 0 {application → }/constraints/linear_constraint.js
  43. 0 {application → }/constraints/linear_expression.js
  44. 0 {application → }/constraints/node/buildfile.js
  45. 0 {application → }/constraints/package.json
  46. 0 {application → }/constraints/point.js
  47. 0 {application → }/constraints/simplex_solver.js
  48. 0 {application → }/constraints/strength.js
  49. 0 {application → }/constraints/symbolic_weight.js
  50. 0 {application → }/constraints/tableau.js
  51. 0 {application → }/constraints/timer.js
  52. 0 {application → }/constraints/variable.js
  53. +52 −2 datastore/models/record.js
  54. +3 −0 datastore/system/store.js
  55. +0 −133 examples/behavior_test/behavior.js
  56. +189 −1 examples/behavior_test/core.js
  57. +36 −4 examples/behavior_test/main.js
  58. +2 −2 examples/form_demo/upper.js
  59. +28 −0 examples/list_demo/core.js
  60. +169 −0 examples/list_demo/main.js
  61. +13 −0 examples/list_demo/node/buildfile.js
  62. +13 −0 examples/list_demo/package.json
  63. +301 −301 examples/view_demo/button_renderer.js
  64. +6 −3 examples/view_demo/main.js
  65. +172 −172 examples/view_demo/sprite_layer.js
  66. +5 −2 examples/widget_demo/buttons.js
  67. +10 −4 examples/widget_demo/controls.js
  68. +6 −0 foundation/system/object.js
  69. +2 −1 projectfile.js
  70. +2 −2 tests/application/qunit/foundation/mixins/responder_context.js
View
43 application/behaviors/behavior.js
@@ -4,12 +4,10 @@
// License: Licensed under the GPLv3 license (see BLOSSOM-LICENSE).
// ==========================================================================
-sc_require('system/responder') ;
-
/** @class
Base class for layer behavior. Behaviors provide two functions:
- 1. They translate state and events into actions on their view and layer
+ 1. They translate state and events into actions on their view and layer
tree updates and redraws, using the statechart approach.
2. They act as first responders for incoming keyboard, mouse, and
@@ -18,31 +16,36 @@ sc_require('system/responder') ;
Behavior Initialization
-----------------------
- There are several methods you can override on SC.Behavior that will be
- called at different times depending on how your behavior is created. Here
+ There are several methods you can override on SC.Behavior that will be
+ called at different times depending on how your behavior is created. Here
is a guide to the main methods you may want to override and when:
- *init:* override this method for any general object setup (such as
observers) that you need to happen whenever the behavior is created.
- - *updateLayer:* override this method to do more complex management of your
- layer tree to reflect the current state of your behavior. This method is
- called whenever displayDidChange() is called, or one of the keys in
- `displayProperties` is changed. By default, it calls render(), passing
- `layer`'s `context` property. If you just need to draw, override
+ - *updateLayer:* override this method to do more complex management of your
+ layer tree to reflect the current state of your behavior. This method is
+ called whenever displayDidChange() is called, or one of the keys in
+ `displayProperties` is changed. By default, it calls render(), passing
+ `layer`'s `context` property. If you just need to draw, override
`render()` instead.
- @extends SC.Responder
+ @extends Function
@since Blossom 1.0
*/
-SC.Behavior = SC.Responder.extend({
-
- isBehavior: true, // Walk like a duck.
-
- dispatch: function(evt) {
-
- },
-
- state: null // a String, maps to a hash
+SC.mixin(Function.prototype,
+/** @scope Function.prototype */ {
+
+ /**
+ Indicates that the function should be treated as a hierarchical behavior.
+
+ @param {String} superbehaviorKey optional property key for superbehavior
+ @returns {Function} the declared function instance
+ */
+ behavior: function(superbehaviorKey) {
+ this.__superbehaviorKey__ = superbehaviorKey;
+ this.isBehavior = true;
+ return this;
+ }
});
View
109 application/behaviors/generic_button.js
@@ -1,109 +0,0 @@
-// ==========================================================================
-// Project: Blossom - Modern, Cross-Platform Application Framework
-// Copyright: ©2012 Fohr Motion Picture Studios. All rights reserved.
-// License: Licensed under the GPLv3 license (see BLOSSOM-LICENSE).
-// ==========================================================================
-/*globals sc_assert */
-
-sc_require('behaviors/behavior') ;
-
-SC.GenericButtonBehavior = SC.Behavior.extend({
-
- // Required
- startState: 'Enable immediately?',
-
- // Optional, used to configure a layer's initial properties.
- defaultState: 'Enabled',
-
- // Required. All named states MUST be unique within the hierarchy.
- states: {
-
- // This is an internal, transient state. It MUST transition to another
- // state, and it will NEVER be visible externally as a state.
- 'Enable immediately?': function() {
- if (!this.get('isEnabled')) this.transition('Disabled');
- else this.transition('Activate immediately?');
- },
-
- // This is also a transient state (same rules as above apply).
- 'Activate immediately?': function() {
- // No special behavior here (hook for subclasses)
- this.transition('Enabled');
- },
-
- 'Inactive': {
-
- 'Enabled': {
- $disable: 'Disabled',
- $activate: 'Ready'
- },
-
- 'Disabled': {
- $enable: 'Activate immediately?'
- }
-
- },
-
- 'Active': {
- '$enter dialogue': 'Dialogue',
-
- 'Ready': {
- $disable: 'Disabled',
- $deactivate: 'Enabled'
- },
-
- 'Invoked': {
- $activate: 'Ready'
- }
- },
-
- 'Dialogue': {
- defaultSubstate: 'Choose Default',
-
- $disable: function() {
- this.triggerAction('cancel');
- this.transition('Disabled');
- },
-
- 'Choose Default': function() {
- switch (this.get('defaultChoice')) {
- case 'cancel':
- this.transition('Choose Cancel');
- break;
- case 'confirm':
- this.transition('Choose Confirm');
- break;
- default:
- console.log("SC.ButtonBehavior: Invalid 'defaultChoice':", this.get('defaultChoice'), "Disabling behavior.");
- this.triggerAction('cancel');
- this.transition('Disabled');
- }
- },
-
- 'Choose Cancel': {
- '$choose confirm': 'Choose Confirm',
- '$exit dialogue': function() {
- this.triggerAction('cancel');
- this.transition('Enabled');
- }
- },
-
- 'Choose Confirm': {
- '$choose cancel': 'Choose Cancel',
- '$exit dialogue': function() {
- this.triggerAction('confirm');
- this.transition('Executed');
- }
- }
- }
-
- },
-
- variables: {
- defaultChoice: 'cancel',
- enable: true
- },
-
- actions: ['cancel', 'confirm']
-
-});
View
74 application/behaviors/mouse_button.js
@@ -1,74 +0,0 @@
-// ==========================================================================
-// Project: Blossom - Modern, Cross-Platform Application Framework
-// Copyright: ©2012 Fohr Motion Picture Studios. All rights reserved.
-// License: Licensed under the GPLv3 license (see BLOSSOM-LICENSE).
-// ==========================================================================
-/*globals sc_assert */
-
-sc_require('behaviors/generic_button');
-
-SC.MouseButtonBehavior = SC.GenericButtonBehavior.extend({
-
- // Required. All named states MUST be unique within the hierarchy. State
- // heirarchy must be consistent with parent behavior everywhere; however,
- // it is acceptable to omit states and state trees that are unchanged from
- // the parent.
- states: {
-
- // Override the generic implementation
- 'Activate immediately?': function(original) {
- var mouse = SC.app.__lastMouseCoordinates__,
- frame = this.__layer__.get('frameInViewport');
-
- if (SC.IsPointInRect(mouse, frame)) {
- this.transition('Ready');
- } else {
- this.transition('Enabled');
- }
- },
-
- 'Inactive': {
-
- 'Enabled': {
- mouseEntered: '$activate' // Maps 'mouseEntered' to the '$activate' author event.
- }
- },
-
- 'Active': {
- mouseDown: '$enter dialogue', // Maps 'mouseDown' to the '$enter dialogue' author event.
-
- 'Ready': {
- mouseExited: '$deactivate' // Maps 'mouseExited' to the '$deactivate' author event.
- },
-
- 'Invoked': {
- enter: function() {
- this.triggerTimeout(300); // In milliseconds. Timeout cleared automatically.
- },
-
- timeout: '$activate', // Maps 'timeout' to the '$activate' author event.
- mouseMoved: '$activate' // Maps 'mouseMoved' to the '$activate' author event.
- }
- },
-
- 'Dialogue': {
-
- 'Choose Cancel': {
- mouseEntered: '$choose confirm', // Maps 'mouseEntered' to the '$choose confirm' author event.
- mouseUp: '$exit dialogue' // Maps 'mouseUp' to the '$exit dialogue' author event.
- },
-
- 'Choose Confirm': {
- mouseExited: '$choose cancel', // Maps 'mouseExited' to the '$choose cancel' author event.
- mouseUp: '$exit dialogue' // Maps 'mouseUp' to the '$exit dialogue' author event.
- }
- }
-
- },
-
- // Overrides our superclass' defaults.
- variables: {
- defaultChoice: 'confirm'
- }
-
-});
View
65 application/behaviors/touch_button.js
@@ -1,65 +0,0 @@
-// ==========================================================================
-// Project: Blossom - Modern, Cross-Platform Application Framework
-// Copyright: ©2012 Fohr Motion Picture Studios. All rights reserved.
-// License: Licensed under the GPLv3 license (see BLOSSOM-LICENSE).
-// ==========================================================================
-/*globals sc_assert */
-
-sc_require('behaviors/generic_button');
-
-SC.TouchButtonBehavior = SC.GenericButtonBehavior.extend({
-
- // Required. All named states MUST be unique within the hierarchy. State
- // heirarchy must be consistent with parent behavior everywhere; however,
- // it is acceptable to omit states and state trees that are unchanged from
- // the parent.
- states: {
-
- // Override the generic implementation
- 'Enable immediately?': function(original) {
- if (!this.get('isEnabled')) this.transition('Disabled');
- else this.transition('Enabled');
- },
-
- // Disable this state and its substates as possible transition targets.
- 'Activate immediately?': null,
-
- 'Inactive': {
-
- 'Enabled': {
- $activate: null, // Disables this author event.
- touchStarted: 'Dialogue'
- }
- },
-
- // Disable this state and its substates as possible transition targets.
- 'Active': null,
-
- 'Dialogue': {
-
- 'Choose Cancel': {
- touchEntered: '$choose confirm', // Maps 'touchEntered' to the '$choose confirm' author event.
-
- touchEnded: '$exit dialogue' // Maps 'touchEnded' to the '$exit dialogue' author event.
- },
-
- 'Choose Confirm': {
- '$exit dialogue': null, // Disables this author event.
-
- touchExited: '$choose cancel', // Maps 'touchExited' to the '$choose cancel' author event.
-
- touchEnded: function(evt) {
- this.triggerAction('confirm');
- this.transition('Enabled');
- }
- }
- }
-
- },
-
- // Overrides our superclass' defaults.
- variables: {
- defaultChoice: 'confirm'
- }
-
-});
View
2 application/core.js
@@ -121,7 +121,7 @@ SC.AugmentBaseClassWithDisplayProperties = function(K) {
if (displayPropertiesHash[key] !== undefined) throw "A displayProperty collides with a predefined name on Object: "+key+". Please use a different name.";
displayPropertiesHash[key] = true;
}
- console.log(displayPropertiesHash);
+ // console.log(displayPropertiesHash);
ret.displayPropertiesHash = displayPropertiesHash;
}
View
13 application/ext/browser.js
@@ -3,8 +3,7 @@
// Copyright: ©2012 Fohr Motion Picture Studios. All rights reserved.
// License: Licensed under the GPLv3 license (see BLOSSOM-LICENSE).
// ==========================================================================
-/*globals sc_assert CanvasRenderingContext2D HTMLCanvasElement
- ENFORCE_BLOSSOM_2DCONTEXT_API */
+/*globals sc_assert CanvasRenderingContext2D HTMLCanvasElement */
var ENFORCE_BLOSSOM_2DCONTEXT_API = false; // removes context.canvas and context.drawImage()
@@ -31,16 +30,6 @@ CanvasRenderingContext2D.prototype.__defineGetter__('h', function() {
CanvasRenderingContext2D.prototype._sc_drawImage = CanvasRenderingContext2D.prototype.drawImage;
CanvasRenderingContext2D.prototype._sc_createPattern = CanvasRenderingContext2D.prototype.createPattern;
-if (ENFORCE_BLOSSOM_2DCONTEXT_API) {
- console.log("*** ENFORCE_BLOSSOM_2DCONTEXT_API is ON ***");
- CanvasRenderingContext2D.prototype.drawImage = function() {
- throw "CanvasRenderingContext2D#drawImage() is not available in Blossom. Use #drawLayer() instead.";
- };
- CanvasRenderingContext2D.prototype.createPattern = function() {
- throw "CanvasRenderingContext2D#createPattern() is not available in Blossom. Use SC.Layer#patternForContext(context, repetition) instead.";
- };
-}
-
CanvasRenderingContext2D.prototype.drawLayer = function(layer, sx, sy, sw, sh, dx, dy, dw, dh) {
if (!layer.isLayer) throw TypeError;
var el = layer.__sc_element__;
View
10 application/ext/run_loop.js
@@ -10,6 +10,10 @@
// view queues and Timers.
var runLoopBenchKey = 'SC.RunLoop#loop()';
+var BREAK = false;
+
+SC.changedStoreKeys = {};
+SC.didChangeStoreKeys = false;
SC.RunLoop = SC.RunLoop.extend(
/** @scope SC.RunLoop.prototype */ {
@@ -18,11 +22,15 @@ SC.RunLoop = SC.RunLoop.extend(
Override so we can benchmark it.
*/
beginRunLoop: function() {
+ // console.log('SC.RunLoop#beginRunLoop()');
SC.Benchmark.start(runLoopBenchKey);
var ret = arguments.callee.base.apply(this, arguments); // do everything else
// sc_assert(SC.animationTransactions.length === 0); // Not true on tested run loops.
SC.AnimationTransaction.begin();
SC.STORE_NEEDS_FINAL_FLUSH = true;
+ // if (SC.didChangeStoreKeys) console.log(SC.changedStoreKeys);
+ SC.changedStoreKeys = {}; // reset
+ SC.didChangeStoreKeys = false;
return ret;
},
@@ -202,6 +210,7 @@ SC.ScheduleLayoutAndRendering = function closure() {
SC.Benchmark.start(benchKey);
didRequestAnimationFrame = false;
SC.isAnimating = true;
+ SC.requestAnimationFrame = false;
SC.app.performLayoutAndRendering(timestamp);
SC.isAnimating = false;
SC.Benchmark.end(benchKey);
@@ -214,6 +223,7 @@ SC.ScheduleLayoutAndRendering = function closure() {
// Viewport size changes occur much less often, so we test second.
if (SC.requestAnimationFrame || force) {
didRequestAnimationFrame = true;
+ console.log('requesting a new animation frame', SC.requestAnimationFrame, force);
SC.RequestAnimationFrame(callback);
}
};
View
11 application/layers/label.js
@@ -43,10 +43,9 @@ SC.LabelLayer = SC.Layer.extend({
}
}.observes('value'),
- updateTextLayout: function() {
+ updateTextLayout: function(context) {
// console.log('SC.LabelLayer#updateTextLayout()');
- var context = this.get('context'),
- str = String(this.get('value') || ''),
+ var str = String(this.get('value') || ''),
width;
this.__needsTextLayout__ = false;
@@ -59,14 +58,12 @@ SC.LabelLayer = SC.Layer.extend({
render: function(ctx) {
// console.log('SC.LabelLayer#render()');
var str = String(this.get('value') || ''),
- w = ctx.w, h = ctx.h,
+ bounds = this.get('bounds'),
+ w = bounds.width, h = bounds.height,
textAlign = this.get('textAlign');
sc_assert(!this.__needsTextLayout__);
- // Always clear the rect in case someone wants transparency.
- ctx.clearRect(0, 0, w, h);
-
ctx.fillStyle = this.get('backgroundColor');
ctx.fillRect(0, 0, w, h);
View
79 application/layers/layer.js
@@ -3,8 +3,7 @@
// Copyright: ©2012 Fohr Motion Picture Studios. All rights reserved.
// License: Licensed under the GPLv3 license (see BLOSSOM-LICENSE).
// ==========================================================================
-/*globals CanvasRenderingContext2D HTMLCanvasElement
- ENFORCE_BLOSSOM_2DCONTEXT_API sc_assert */
+/*globals CanvasRenderingContext2D HTMLCanvasElement sc_assert */
sc_require('system/matrix');
sc_require('ext/browser');
@@ -98,13 +97,9 @@ SC.Layer = SC.Object.extend({
},
updateLayout: function(layersNeedingTextLayout) {
- console.log('SC.Layer#updateLayout()', SC.guidFor(this));
+ // console.log('SC.Layer#updateLayout()', SC.guidFor(this));
if (this.__needsLayout__) {
- var bounds = this.get('bounds'), // Sets this.__needsLayout__ to false.
- canvas = this.__sc_element__;
-
- canvas.width = bounds.width;
- canvas.height = bounds.height;
+ var bounds = this.get('bounds'); // Sets this.__needsLayout__ to false.
this._sc_transformFromSuperlayerToLayerIsDirty = true; // HACK
this._sc_computeTransformFromSuperlayerToLayer();
}
@@ -163,6 +158,31 @@ SC.Layer = SC.Object.extend({
ctx.restore();
},
+ renderIntoContext: function(ctx) {
+ // console.log('SC.Layer#renderIntoContext()', SC.guidFor(this));
+ sc_assert(!this._sc_transformFromSuperlayerToLayerIsDirty);
+ var t = this._sc_transformFromSuperlayerToLayer;
+
+ if (!this.get('isVisible')) return;
+
+ ctx.save();
+ sc_assert(Math.round(t[4]) === t[4]);
+ sc_assert(Math.round(t[5]) === t[5]);
+ // console.log(t[0], t[1], t[2], t[3], t[4], t[5], SC.guidFor(this));
+ ctx.transform(t[0], t[1], t[2], t[3], t[4], t[5]);
+
+ this.renderBoundsPath(ctx, true);
+ ctx.clip();
+
+ var delegate = this.get('delegate');
+ if (delegate && delegate.render) delegate.render(ctx, this);
+ else this.render(ctx);
+ this.__needsRendering__ = false;
+
+ this.get('sublayers').invoke('renderIntoContext', ctx);
+ ctx.restore();
+ },
+
// ..........................................................
// SURFACE SUPPORT
//
@@ -285,6 +305,8 @@ SC.Layer = SC.Object.extend({
} else return this._sc_position;
}.property(),
+ __forceWidthHeight__: false,
+
/**
Specifies the bounds rectangle of the receiver. Animatable.
@@ -318,6 +340,13 @@ SC.Layer = SC.Object.extend({
};
}
+ if (this.__forceWidthHeight__) {
+ pbounds = {
+ width: this.get('width') || 0,
+ height: this.get('height') || 0
+ };
+ }
+
// This updates `position` and `bounds`.
this._sc_layoutFunction(
this._sc_layoutValues,
@@ -559,8 +588,7 @@ SC.Layer = SC.Object.extend({
Clears the layer.
*/
clear: function() {
- var context = this.get('context');
- if (context) context.clearRect(0, 0, context.width, context.height);
+ console.log('SC.Layer#clear() is deprecated.');
},
initElement: function() {
@@ -572,8 +600,6 @@ SC.Layer = SC.Object.extend({
this.__sc_element__ = canvas;
context.__sc_canvas__ = canvas;
- if (ENFORCE_BLOSSOM_2DCONTEXT_API) delete context.canvas;
-
canvas.id = this.get('id');
canvas.width = bounds[2]/*width*/;
canvas.height = bounds[3]/*height*/;
@@ -725,7 +751,7 @@ SC.Layer = SC.Object.extend({
// This is a specialized initializer for our subclasses, so that each
// subclass can create their own backing layer type (canvas, video, etc.).
- this.initElement();
+ // this.initElement();
// debugger;
this.__needsLayout__ = true;
@@ -768,6 +794,7 @@ SC.Layer = SC.Object.extend({
},
_sc_computeTransformFromSuperlayerToLayer: function() {
+ // console.log('SC.Layer#_sc_computeTransformFromSuperlayerToLayer()', SC.guidFor(this));
// Assume our callers have checked to determine if we should be called.
// if (!this._sc_transformFromSuperlayerToLayerIsDirty) return;
sc_assert(this._sc_transformFromSuperlayerToLayerIsDirty);
@@ -867,7 +894,7 @@ SC.Layer = SC.Object.extend({
SC.RectApplyAffineTransformTo(rect, tmpTransform, dest);
},
- renderBoundsPath: function(context) {
+ renderBoundsPath: function(context, isClip) {
var b = this.get('bounds'),
cornerRadius = this.get('cornerRadius');
@@ -875,17 +902,25 @@ SC.Layer = SC.Object.extend({
// adjusted so that bounds.x and bounds.y are positioned at (0,0).
if (cornerRadius > 0) {
var width = b[2]/*width*/,
- height = b[3]/*height*/;
+ height = b[3]/*height*/,
+ zero = 0;
+
+ // Slightly grow the clipping to enable anti-aliasing
+ if (isClip) {
+ width += 1;
+ height += 1;
+ zero = -0.5;
+ }
- context.moveTo(cornerRadius, 0);
- context.lineTo(width - cornerRadius, 0);
- context.quadraticCurveTo(width, 0, width, cornerRadius);
+ context.moveTo(cornerRadius, zero);
+ context.lineTo(width - cornerRadius, zero);
+ context.quadraticCurveTo(width, zero, width, cornerRadius);
context.lineTo(width, height - cornerRadius);
context.quadraticCurveTo(width, height, width - cornerRadius, height);
context.lineTo(cornerRadius, height);
- context.quadraticCurveTo(0, height, 0, height - cornerRadius);
+ context.quadraticCurveTo(0, height, zero, height - cornerRadius);
context.lineTo(0, cornerRadius);
- context.quadraticCurveTo(0, 0, cornerRadius, 0);
+ context.quadraticCurveTo(zero, zero, cornerRadius, zero);
context.closePath();
} else {
context.rect(0, 0, b[2]/*width*/, b[3]/*height*/);
@@ -950,7 +985,7 @@ SC.Layer = SC.Object.extend({
},
addSublayer: function(layer) {
- sc_assert(layer && layer.instanceOf(SC.Layer));
+ sc_assert(layer && layer.kindOf(SC.Layer));
sc_assert(!layer.get('superlayer'));
var sublayers = this.get('sublayers');
@@ -959,7 +994,7 @@ SC.Layer = SC.Object.extend({
},
removeSublayer: function(layer) {
- sc_assert(layer && layer.instanceOf(SC.Layer));
+ sc_assert(layer && layer.kindOf(SC.Layer));
sc_assert(layer.get('superlayer') === this);
var sublayers = this.get('sublayers');
View
22 application/layers/text.js
@@ -51,10 +51,9 @@ SC.TextLayer = SC.Layer.extend({
}
}.observes('value'),
- updateTextLayout: function() {
- console.log('SC.TextLayer#updateTextLayout()');
- var context = this.get('context'),
- text = String(this.get('value') || ''),
+ updateTextLayout: function(context) {
+ // console.log('SC.TextLayer#updateTextLayout()');
+ var text = String(this.get('value') || ''),
line, that = this;
this.__needsTextLayout__ = false;
@@ -113,7 +112,7 @@ SC.TextLayer = SC.Layer.extend({
} else {
height = Math.max(that.get('layout').minHeight || 0, lines.length*that.get('lineHeight'));
}
- that.__sc_element__.height = that._sc_bounds[3]/*height*/ = height;
+ that._sc_bounds[3]/*height*/ = height;
that.triggerRendering();
} else {
console.log('Paragraph can not be set with the given tolerance.', tolerance);
@@ -130,16 +129,17 @@ SC.TextLayer = SC.Layer.extend({
lineLengths = [this.get('bounds')[2]/*width*/],
maxLength = Math.max.apply(null, lineLengths),
lineHeight = this.get('lineHeight'),
- center = false, y = 0;
+ center = false, y = 0,
+ bounds = this.get('bounds'),
+ backgroundColor = this.get('backgroundColor');
sc_assert(!this.__needsTextLayout__);
sc_assert(lines);
- // Always clear the rect in case someone wants transparency.
- context.clearRect(0, 0, context.width, context.height);
-
- context.fillStyle = this.get('backgroundColor');
- context.fillRect(0, 0, context.width, context.height);
+ if (backgroundColor) {
+ context.fillStyle = backgroundColor;
+ context.fillRect(0, 0, bounds.width, bounds.height);
+ }
context.textBaseline = this.get('textBaseline');
context.font = this.get('font');
View
23 application/mixins/string.js
@@ -53,6 +53,29 @@ SC.mixin(SC.String, {
if (SC.typeOf(str) !== SC.T_STRING) str = this;
var args = SC.$A(arguments); args.shift(); // remove def param
return str.fmt.apply(str,args) ;
+ },
+
+ /**
+ Accepts canvas 2D rendering context and maximum pixel length arguments
+ and returns a string that does not exceed the maximum width. If the
+ original string is too long for the width it will be truncated with
+ elipses placed at the beginning of a text aligned right context or the
+ end of a text aligned left or center context.
+
+ @param {CanvasRenderingContext2D} context
+ @param {Number) maxLength maximum length in pixels of the resulting
+ */
+ elide: function(context, maxLength) {
+ if (maxLength <= 0) return '';
+ var ret = this, isRight = context.textAlign === 'right';
+ if (context.measureText(ret).width > maxLength) {
+ var e = '...', len = context.measureText(e).width;
+ while (context.measureText(ret).width+len > maxLength) {
+ ret = ret.slice(0, ret.length-1);
+ }
+ ret = ret+e;
+ }
+ return ret;
}
});
View
2 application/node/buildfile.js
@@ -12,6 +12,6 @@ module.exports = BT.Framework.create({
sourceTree: path.join(__dirname, ".."),
- "constraints": require('../constraints/node/buildfile')
+ "constraints": require('../../constraints/node/buildfile')
});
View
974 application/surfaces/ilist_view.js
@@ -0,0 +1,974 @@
+// ==========================================================================
+// Project: Blossom - Modern, Cross-Platform Application Framework
+// Copyright: ©2012 Fohr Motion Picture Studios. All rights reserved.
+// License: Licensed under the GPLv3 license (see BLOSSOM-LICENSE).
+// ==========================================================================
+/*globals sc_assert */
+
+sc_require('surfaces/view');
+
+var base03 = "#002b36";
+var base02 = "#073642";
+var base01 = "#586e75";
+var base00 = "#657b83";
+var base0 = "#839496";
+var base1 = "#93a1a1";
+var base2 = "#eee8d5";
+var base3 = "#fdf6e3";
+var yellow = "#b58900";
+var orange = "#cb4b16";
+var red = "#dc322f";
+var magenta = "#d33682";
+var violet = "#6c71c4";
+var blue = "#268bd2";
+var cyan = "#2aa198";
+var green = "#859900";
+var white = "white";
+
+/** @class
+ `SC.ListView` implements a scrollable view. You can use it
+ interchangeably with `SC.View`, the only difference is the scrolling.
+
+ Setting the bounds of the scroll is important.
+
+ @extends SC.ScrollView
+ @since Blossom 1.0
+*/
+SC.IListView = SC.View.extend({
+
+
+ __tagName__: 'div',
+
+ __useContentSize__: false,
+
+ isCompositeSurface: true, // Walk like a duck.
+ subsurfaces: function() {
+ return [this._sc_scrollingSurface];
+ }.property(),
+
+ /**
+ true if the view should maintain a horizontal scroller. This property
+ must be set when the view is created.
+
+ @property {Boolean}
+ */
+ hasHorizontalScroller: true,
+
+ /**
+ true if the view should maintain a horizontal scroller. This property
+ must be set when the view is created.
+
+ @property {Boolean}
+ */
+ hasVerticalScroller: true,
+
+ // ..........................................................
+ // PSURFACE SUPPORT (Private)
+ //
+
+ updatePsurface: function(psurface, surfaces) {
+ // console.log('SC.IListView#updatePsurface()');
+
+ sc_assert(this === SC.surfaces[this.__id__], "SC.Surface#updatePsurface() can only be called on active surfaces.");
+
+ // Sanity check the Psurface.
+ sc_assert(psurface);
+ sc_assert(psurface instanceof SC.Psurface);
+ sc_assert(psurface.__element__);
+ sc_assert(psurface.__element__ === document.getElementById(this.__id__));
+
+ var surface = this._sc_scrollingSurface;
+ if (surfaces) surfaces[surface.__id__] = surface;
+ var cur = psurface.push(surface);
+ surface.updatePsurface(cur, surfaces);
+ cur.pop();
+ },
+
+ updateLayout: function() {
+ // console.log('SC.IListView#updateLayout()');
+ arguments.callee.base.apply(this, arguments);
+
+ this.adjustLayout();
+ },
+
+ content: null,
+ contentBindingDefault: SC.Binding.multiple(),
+
+ _sc_hasScrollListener: false,
+ didCreateElement: function(div) {
+ // We don't want SC.View's implementation; don't call it.
+ div.style.overflowX = this.get('hasHorizontalScroller')? 'scroll' : 'hidden';
+ div.style.overflowY = this.get('hasVerticalScroller')? 'scroll' : 'hidden';
+
+ // FIXME: This should be done dynamically, per scrollview. I'm not doing
+ // it now because the CSS has pseudo-selectors, so I have to generate
+ // stylesheet code specially. (Here and a few other places, actually.)
+ //
+ // For now, I'll specially customize the CSS to work with Postbooks' UI
+ // correctly.
+ div.className = 'frame';
+
+ // This should probably only be set on mobile Safari/Google Chrome for
+ // Android.
+ //
+ // See http://stackoverflow.com/questions/7763458/ios5-webkit-overflow-scrolling-causes-touch-events-stopping-work
+ // for a fix I haven't yet implemented, too.
+ div.style.webkitOverflowScrolling = 'touch';
+
+ // We have to establish this stuff every time we set up our DOM.
+ SC.Event.add(div, 'scroll', this, this._sc_didScroll);
+ this._sc_hasScrollListener = true;
+ this._sc_scrollTopPrev = 0;
+ this._sc_didNotRender = true;
+ this._sc_scrollDelay = 0;
+ },
+
+ _sc_scrollTopPrev: 0,
+ _sc_didNotRender: true,
+ _sc_scrollDelay: 0,
+ _sc_rowIndex: 0,
+ _sc_rowLength: 0,
+
+ _sc_didScroll: function(evt) {
+ // console.log('did scroll');
+ var now = new Date().getTime(),
+ delay = this._sc_scrollDelay,
+ height = this.get('rowHeight'),
+ canvas = this._sc_context.__sc_canvas__,
+ didNotDraw = true,
+ rect, top, prev,
+ div = SC.psurfaces[this.__id__].__element__;
+
+ rect = SC.psurfaces[this._sc_scrollingSurface.__id__].__element__.getBoundingClientRect();
+ top = -rect.top;
+ prev = this._sc_scrollTopPrev;
+
+ if (this._sc_hasScrollListener) {
+ SC.Event.remove(div, 'scroll', this, this._sc_didScroll);
+ this._sc_hasScrollListener = false;
+ }
+
+ if (Math.abs(top - prev) < rect.height*0.10) {
+ if (now - delay > 150) {
+ // console.log('drawing');
+ didNotDraw = false;
+ // var canvasTop = Math.min(Math.floor((top/(height*3)))*(height*3), 300000);
+ var scrollframeHeight = this.get('frame').height,
+ listHeight = this._sc_scrollingSurface.get('frame').height,
+ canvasHeight = this._sc_scrollingSurface._sc_scrollingCanvas.get('frame').height,
+ offset = rect.top - div.getBoundingClientRect().top,
+ rowHeight = this.get('rowHeight');
+
+
+ // 1.
+ var x = -offset + (scrollframeHeight/2) - (canvasHeight/2);
+
+ // 2.
+ var y = x - (x % rowHeight);
+
+ // 3.
+ var z = Math.min(y, listHeight - canvasHeight);
+
+ // 4.
+ offset = z < 0 ? 0 : z;
+ // console.log(x, y, z, offset);
+
+ // sc_assert(offset % rowHeight === 0);
+ // sc_assert(offset >= 0 && offset <= listHeight - canvasHeight);
+
+ canvas.style.top = offset + 'px';
+ this.triggerRendering();
+ this._sc_rowIndex = offset/rowHeight;
+ }
+ } else {
+ delay = new Date().getTime();
+ }
+
+ if (didNotDraw || top !== prev) {
+ // SC.requestAnimationFrame = true;
+ var that = this;
+ SC.RequestAnimationFrame(function() {
+ SC.RunLoop.begin();
+ that._sc_didScroll();
+ SC.RunLoop.end();
+ });
+ this._sc_scrollTopPrev = top;
+ } else {
+ SC.Event.add(div, 'scroll', this, this._sc_didScroll);
+ this._sc_hasScrollListener = true;
+ }
+ },
+
+ // ..........................................................
+ // SELECTION SUPPORT
+ //
+
+ selection: null,
+
+ _sc_selection: null,
+ _sc_selectionDidChange: function() {
+ var old = this._sc_selection,
+ cur = this.get('selection'),
+ func = this.triggerRendering;
+
+ if (old === cur) return; // nothing to do
+
+ if (old) old.removeObserver('[]', this, func);
+ this._sc_selection = cur;
+ if (cur) cur.addObserver('[]', this, func);
+
+ this.triggerRendering();
+ }.observes('selection'),
+
+ rowHeight: 30,
+
+ renderRow: function(context, width, height, index, object, isSelected, isLast) {
+ context.fillStyle = isSelected? '#99CCFF' : 'white';
+ context.fillRect(0, 0, width, height);
+
+ context.strokeStyle = 'grey';
+ context.lineWidth = 1;
+
+ context.beginPath();
+ context.moveTo(0, height - 0.5);
+ context.lineTo(width, height - 0.5);
+ context.stroke();
+
+ context.font = "12pt Helvetica";
+ context.fillStyle = 'black';
+ context.textAlign = 'center';
+ context.textBaseline = 'middle';
+
+ context.fillText(String(index), width/2, height/2);
+ },
+
+ hasHorizontalScroller: false,
+ clearBackground: true,
+
+ _sc_plistItems: null,
+
+ updateDisplay: function() {
+ // console.log('SC.IListView#updateDisplay()', SC.guidFor(this));
+ var benchKey = 'SC.IListView#updateDisplay()';
+ SC.Benchmark.start(benchKey);
+
+ var ctx = this._sc_context;
+ sc_assert(ctx);
+ sc_assert(document.getElementById(ctx.__sc_canvas__.id));
+
+ // FIXME: Clear the background if requested. We don't really want to do this.
+ // if (this.get('clearBackground')) ctx.clearRect(0, 0, ctx.w, ctx.h);
+ //
+ // if (this.willRenderLayers) {
+ // ctx.save();
+ // this.willRenderLayers(ctx);
+ // ctx.restore();
+ // }
+
+ var content = this.get('content'),
+ selection = this.get('selection'),
+ rowIndex = this._sc_rowIndex,
+ len = this._sc_rowLength,
+ w = ctx.w, h = this.get('rowHeight'),
+ plistItems = this._sc_plistItems,
+ newPlistItems = {};
+
+ // If we have fewer rows (no scrolling), don't try and render too much.
+ len = Math.min(len, (content? content.get('length') : 0) - rowIndex);
+ // console.log(idx, len);
+
+ var changedStoreKeys = SC.changedStoreKeys;
+ var needsRendering = false;
+
+ // Create this here so we don't have to make so many.
+ var processLayer = function(layer) {
+ if (layer.__needsRendering__) needsRendering = true;
+ else layer.get('sublayers').forEach(processLayer);
+ };
+
+ if (content && len > 0) {
+
+ for (var idx = 0; idx<len; ++idx) {
+ var obj = content.objectAt(idx + rowIndex),
+ storeKey = obj.storeKey;
+
+ sc_assert(storeKey);
+ var plistItem = plistItems[storeKey];
+ if (plistItem) {
+ delete plistItems[storeKey]; // We've processed it.
+ newPlistItems[storeKey] = plistItem;
+
+ // We need to update this plist item and determine if it should be
+ // re-rendered. First we check the properties dependent on the
+ // list view itself.
+ if (plistItem.index !== idx + rowIndex) {
+ plistItem.index = idx + rowIndex;
+ needsRendering = true;
+ }
+
+ if (plistItem.offset !== idx*h) {
+ plistItem.offset = idx*h;
+ needsRendering = true;
+ }
+
+ var isSelected = selection.contains(obj);
+ if (plistItem.isSelected !== isSelected) {
+ plistItem.isSelected = isSelected;
+ needsRendering = true;
+ }
+
+ var isLast = (idx + rowIndex) === len - 1;
+ if (plistItem.isLast !== isLast) {
+ plistItem.isLast = isSelected;
+ needsRendering = true;
+ }
+
+ // Next we check the storeKeys for changes *if*
+ if (!needsRendering && !plistItem.needsRendering) {
+ if (changedStoreKeys[storeKey]) needsRendering = true;
+
+ // Okay, check our dependent keys.
+ if (!needsRendering) {
+ var dependentKeys = plistItem.dependentKeys;
+ if (dependentKeys) {
+ for (var dependentKey in dependentKey) {
+ if (!dependentKeys.hasOwnProperty(dependentKey)) continue;
+ if (changedStoreKeys[dependentKey]) {
+ needsRendering = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // Check the layer tree, if it exists
+ if (!needsRendering) {
+ var layerTree = plistItem.editableLayerTree;
+ if (!layerTree) layerTree = plistItem.mouseLayerTree;
+ if (!layerTree) layerTree = plistItem.renderLayerTree;
+
+ if (layerTree) {
+ if (layerTree.__needsRendering__) needsRendering = true;
+ else layerTree.get('sublayers').forEach(processLayer);
+ }
+ }
+
+ if (needsRendering) plistItem.needsRendering = true;
+
+ // Create a new plistItem
+ } else {
+ plistItem = newPlistItems[storeKey] = new SC.PListItem(idx + rowIndex, obj, idx*h);
+ plistItem.isSelected = selection.contains(obj);
+ plistItem.isLast = (idx + rowIndex) === len - 1;
+ // item is already marked as needing rendering on init
+ }
+ }
+ }
+
+ // Handle plist items that are no longer with us.
+ var isInputSurface = SC.app.get('inputSurface') === this;
+ for (var oldStoreKey in plistItems) {
+ if (!plistItems.hasOwnProperty(oldStoreKey)) continue;
+ plistItem = plistItems[oldStoreKey];
+
+ var editableLayerTree = plistItem.editableLayerTree;
+ if (editableLayerTree && isInputSurface) SC.CloseFieldEditor();
+
+ // Release stuff.
+ delete plistItem.object;
+ delete plistItem.renderLayerTree;
+ delete plistItem.mouseLayerTree;
+ delete plistItem.editableLayerTree;
+ }
+
+ // Keep for next round.
+ this._sc_plistItems = newPlistItems;
+
+ var clearBackground = this.get('clearBackground'),
+ backgroundColor = this.get('backgroundColor');
+
+ // Draw plist items as needed.
+ for (storeKey in newPlistItems) {
+ if (!newPlistItems.hasOwnProperty(storeKey)) continue;
+ plistItem = newPlistItems[storeKey];
+ if (plistItem.needsRendering) {
+ plistItem.needsRendering = false;
+ ctx.save();
+ ctx.translate(0, plistItem.offset);
+
+ // We render the most complicated option.
+ layerTree = plistItem.editableLayerTree;
+ if (!layerTree) layerTree = plistItem.mouseLayerTree;
+ if (!layerTree) layerTree = plistItem.renderLayerTree;
+
+ var renderFunction = plistItem.renderFunction;
+ if (!layerTree && !renderFunction) {
+ // We need to find or create the correct one.
+ if (this.createRenderLayerTree) {
+ // console.log('creating render tree');
+ layerTree = plistItem.renderLayerTree = this.createRenderLayerTree();
+
+ if (layerTree) {
+ // sc_assert(layerTree);
+ // sc_assert(layerTree.kindOf(SC.Layer));
+
+ layerTree.__forceWidthHeight__ = true;
+ layerTree.set('width', w);
+ layerTree.set('height', h);
+ layerTree.__needsLayout__ = true;
+ }
+ } else {
+ // FIXME: Seems like this could be done better...
+ if (this.renderRow) {
+ renderFunction = plistItem.renderFunction = this.renderRow;
+ }
+ }
+ }
+
+ // Render with either the layer tree or the render function.
+ if (layerTree) {
+ // Set the properties for the layer tree.
+ layerTree.set('rowIndex', plistItem.index);
+ layerTree.set('content', plistItem.object);
+ layerTree.set('isSelected', plistItem.isSelected);
+ layerTree.set('isLast', plistItem.isLast);
+
+ if (layerTree.__needsLayout__) {
+ // Update the layout.
+ var textLayersNeedingLayout = [];
+ layerTree.updateLayout(textLayersNeedingLayout);
+
+ // Update any text layouts.
+ for (idx=0, len=textLayersNeedingLayout.length; idx<len; ++idx) {
+ textLayersNeedingLayout[idx].updateTextLayout(ctx);
+ }
+ }
+
+ if (clearBackground) ctx.clearRect(0, 0, w, h);
+ else {
+ // We need to draw the background color, even though it is also
+ // applied with CSS, in order to get correct anti-aliasing.
+ ctx.fillStyle = backgroundColor;
+ ctx.fillRect(0, 0, w, h);
+ }
+
+ layerTree.renderIntoContext(ctx);
+
+ } else if (renderFunction) {
+ if (clearBackground) ctx.clearRect(0, 0, w, h);
+ else {
+ // We need to draw the background color, even though it is also
+ // applied with CSS, in order to get correct anti-aliasing.
+ ctx.fillStyle = backgroundColor;
+ ctx.fillRect(0, 0, w, h);
+ }
+
+ renderFunction(ctx, w, h, plistItem.index, plistItem.object, plistItem.isSelected, plistItem.isLast);
+ }
+
+ ctx.restore();
+ }
+ }
+
+ // if (this.didRenderLayers) {
+ // ctx.save();
+ // this.didRenderLayers(ctx);
+ // ctx.restore();
+ // }
+
+ SC.Benchmark.end(benchKey);
+ },
+
+ /**
+ Finds the layer that is hit by this event, and returns its behavior if it
+ exists. Otherwise, returns the receiver.
+ */
+ targetResponderForEvent: function(evt) {
+ // console.log('ListDemo.LayerListView#targetResponderForEvent(', evt.type, ')');
+ var context = this._sc_hitTestCanvas.getContext('2d'),
+ hitLayer = null, zIndex = -1,
+ boundingRect, x, y;
+
+ if (evt.pageX === undefined) return this;
+
+ // debugger;
+ boundingRect = evt.target.getBoundingClientRect();
+ x = evt.clientX - boundingRect.left + window.pageXOffset;
+ y = evt.clientY - boundingRect.top + window.pageYOffset;
+ // console.log('*****', evt.type, x, y, '*****');
+
+ // FIXME: calculate the correct rowIndex!
+ var rowIndex = Math.floor((evt.pageY - boundingRect.top) / this.get('rowHeight')) + this._sc_rowIndex;
+ sc_assert(!isNaN(rowIndex));
+ // console.log('rowIndex', rowIndex);
+
+ function spaces(depth) {
+ console.log(depth);
+ var ret = "", idx, len;
+ for (idx = 0, len = depth; idx<len; ++idx) ret += "--";
+ return ret;
+ }
+
+ function hitTestLayer(layer, point, depth) {
+ // debugger;
+ // console.log(spaces(depth), "on entry:", point.x, point.y);
+ if (layer.get('isHidden')) return;
+ context.save();
+
+ // Prevent this layer and any sublayer from drawing paths outside our
+ // bounds.
+ layer.renderBoundsPath(context);
+ context.clip();
+
+ // Make sure the layer's transform is current.
+ if (layer._sc_transformFromSuperlayerToLayerIsDirty) {
+ layer._sc_computeTransformFromSuperlayerToLayer();
+ }
+
+ // Apply the sublayer's transform from our layer (it's superlayer).
+ var t = layer._sc_transformFromSuperlayerToLayer;
+ context.transform(t[0], t[1], t[2], t[3], t[4], t[5]);
+ SC.PointApplyAffineTransformTo(point, t, point);
+ var frame = layer.get('frame');
+ // console.log(spaces(depth), 'frame:', frame.x, frame.y, frame.width, frame.height);
+ // console.log(spaces(depth), 'transformed:', point.x, point.y);
+
+ // First, test our sublayers.
+ var sublayers = layer.get('sublayers'), idx = sublayers.length;
+ depth++;
+ while (idx--) {
+ hitTestLayer(sublayers[idx], SC.MakePoint(point), depth);
+ }
+
+ // Only test ourself if (a) no hit has been found, or (b) our zIndex is
+ // higher than whatever hit has been found so far.
+ var layerZ = layer.get('zIndex');
+ if (!hitLayer || zIndex < layerZ) {
+ if (layer.hitsPointInContext(x, y, context)) {
+ evt.hitPoint = SC.MakePoint(x - point[0]/*x*/, y - point[1]/*y*/);
+ hitLayer = layer;
+ zIndex = layerZ;
+ }
+ }
+
+ context.restore();
+ }
+
+ // Next, begin the hit testing process. When this completes, hitLayer
+ // will contain the layer that was hit with the highest zIndex.
+ var plistItems = this._sc_plistItems,
+ rowHeight = this.get('rowHeight'),
+ mouseY = evt.pageY - boundingRect.top,
+ layerTree, offset;
+
+ for (var storeKey in plistItems) {
+ if (!plistItems.hasOwnProperty(storeKey)) continue;
+ var plistItem = plistItems[storeKey];
+ offset = plistItem.offset; // this is relative to the canvas
+ if (offset < mouseY && mouseY < (offset + rowHeight)) {
+ layerTree = plistItem.editableLayerTree;
+ if (!layerTree) layerTree = plistItem.mouseLayerTree;
+ if (!layerTree) layerTree = plistItem.renderLayerTree;
+ if (!layerTree) {
+ return this; // row is using a render function
+ }
+ break;
+ }
+ }
+
+ if (!layerTree) return this;
+
+ context.save();
+ context.translate(0, (rowIndex - this._sc_rowIndex)*this.get('rowHeight'));
+ hitTestLayer(layerTree, SC.MakePoint(), 0);
+ context.restore();
+
+ // We don't need to test `layer`, because we already know it was hit when
+ // this method is called by SC.RootResponder.
+ if (!hitLayer) return this;
+ else {
+ // this.triggerRendering();
+ // if (evt.type === 'mousedown') console.log('rowIndex', rowIndex);
+
+ // If we hit a layer, remember it so our view knows.
+ evt.layer = hitLayer;
+
+ evt.hitPoint.y = evt.hitPoint.y - offset;
+
+ hitLayer.set('surface', this._sc_scrollingSurface._sc_scrollingCanvas);
+ var content = this.get('content');
+ hitLayer.set('content', content.objectAt(rowIndex));
+ // if (evt.type === 'mousedown') console.log('content.objectAt(rowIndex).get(\'index\')', content.objectAt(rowIndex).get('index'));
+
+ // Try and find the behavior attached to this layer.
+ var behavior = hitLayer.get('behavior');
+ while (!behavior && hitLayer) {
+ hitLayer = hitLayer.get('superlayer');
+ if (hitLayer) behavior = hitLayer.get('behavior');
+ }
+ return behavior || this;
+ }
+ },
+
+ mouseDown: function(evt) {
+ // console.log('SC.ListView#mouseDown()', SC.guidFor(this));
+ var top = evt.target.getBoundingClientRect().top;
+ this._scrollTop = top;
+ this._scrollTarget = evt.target;
+ this._rowIndex = Math.floor((evt.pageY - top) / this.get('rowHeight')) + this._sc_rowIndex;
+ console.log('this._rowIndex', this._rowIndex);
+ evt.allowDefault();
+ return true;
+ },
+
+ // FIXME: This behavior is only needed on touch devices!
+ mouseUp: function(evt) {
+ var idx = this._rowIndex,
+ content = this._sc_content,
+ selection = this._sc_selection,
+ scrollTarget = this._scrollTarget;
+
+ this._scrollTarget = null;
+
+ if (Math.abs(this._scrollTop - scrollTarget.getBoundingClientRect().top) > 15) {
+ return; // We're scrolling...
+ }
+
+ if (content && selection) {
+ var obj = content.objectAt(idx);
+ if (obj && !selection.contains(obj)) {
+ var sel = SC.SelectionSet.create();
+ sel.addObject(obj);
+ this.set('selection', sel.freeze());
+
+ var action = this.get('action');
+ if (action && typeof action === 'function') {
+ action.call(this, obj, idx);
+ }
+ // TODO: Support the usual target/action paradigm.
+ }
+ }
+ },
+
+ // performLayoutIfNeeded: function(timestamp) {
+ // console.log('SC.IListView#performLayoutIfNeeded()', SC.guidFor(this));
+ // arguments.callee.base.apply(this, arguments);
+ // },
+
+ performRenderingIfNeeded: function(timestamp) {
+ // console.log('SC.IListView#performRenderingIfNeeded()', SC.guidFor(this));
+ this.__needsRendering__ = true; // We do all our work in updateDisplay()
+ arguments.callee.base.apply(this, arguments);
+ },
+
+ adjustLayout: function() {
+ // console.log('SC.IListView#adjustLayout()', SC.guidFor(this));
+ var benchKey = 'SC.ListView#adjustLayout()';
+ SC.Benchmark.start(benchKey);
+
+ var frame = SC.MakeRect(this.get('frame')),
+ content = this.get('content'),
+ rowHeight = this.get('rowHeight');
+
+ var rows = content? content.get('length') : 0;
+
+ frame[0]/*x*/ = 0;
+ frame[1]/*y*/ = 0;
+ frame[2]/*w*/ = frame[2]/*w*/ ; // - 15; // account for scroller
+ frame[3]/*h*/ = Math.max(rows*rowHeight, frame[3]/*h*/);
+
+ // We never have to offset in this manner.
+ var scrollTranslation = this._sc_scrollTranslation;
+ scrollTranslation[0]/*x*/ = 0;
+ scrollTranslation[1]/*y*/ = 0;
+
+ this._sc_scrollingSurface.set('frame', frame);
+ this._sc_scrollingSurface.__needsLayout__ = true;
+
+ SC.Benchmark.end(benchKey);
+ },
+
+ _sc_content: null,
+ _sc_contentPropertyDidChange: function() {
+ // console.log('SC.ListView#_sc_contentPropertyDidChange()', SC.guidFor(this));
+ var func = this._sc_contentLengthDidChange,
+ old = this._sc_content,
+ cur = this.get('content');
+
+ if (old === cur) return;
+
+ if (old) {
+ this._sc_removeContentRangeObserver();
+ old.removeObserver('length', this, func);
+ }
+
+ this._sc_content = cur;
+
+ if (cur) {
+ cur.addObserver('length', this, func);
+ this._sc_updateContentRangeObserver();
+ }
+
+ this._sc_contentLengthDidChange();
+ }.observes('content'),
+
+ _sc_contentLengthDidChange: function() {
+ // console.log('SC.ListView#_sc_contentLengthDidChange()', SC.guidFor(this));
+ this._sc_updateContentRangeObserver();
+ this.triggerLayoutAndRendering();
+ },
+
+ _sc_contentRangeObserver: null,
+ _sc_updateContentRangeObserver: function() {
+ // console.log('SC.ListView#_sc_updateContentRangeObserver()', SC.guidFor(this));
+ var observer = this._sc_contentRangeObserver,
+ content = this.get('content');
+
+ if (!content) return ; // nothing to do
+
+ var nowShowing = SC.IndexSet.create(0, content.get('length'));
+
+ if (observer) {
+ content.updateRangeObserver(observer, nowShowing);
+ } else {
+ var func = this._sc_contentRangeDidChange;
+ observer = content.addRangeObserver(nowShowing, this, func, null, true);
+ this._sc_contentRangeObserver = observer;
+ }
+ },
+
+ _sc_contentRangeDidChange: function() {
+ // console.log('SC.ListView#_sc_contentRangeDidChange()', SC.guidFor(this));
+ this.triggerRendering();
+ },
+
+ _sc_removeContentRangeObserver: function() {
+ // console.log('SC.ListView#_sc_removeContentRangeObserver()', SC.guidFor(this));
+ var content = this.get('content'),
+ observer = this._sc_contentRangeObserver ;
+
+ if (observer) {
+ if (content) content.removeRangeObserver(observer);
+ this._sc_contentRangeObserver = null ;
+ }
+ },
+
+ _sc_scrollingSurface: null,
+
+ _sc_compositeIsPresentInViewportDidChange: function() {
+ // console.log("SC.IListViewSurface#_sc_compositeIsPresentInViewportDidChange()");
+ var isPresentInViewport = this.get('isPresentInViewport');
+ this._sc_scrollingSurface.set('isPresentInViewport', isPresentInViewport);
+ }.observes('isPresentInViewport'),
+
+ init: function() {
+ arguments.callee.base.apply(this, arguments);
+ var scrollingSurface;
+ scrollingSurface = this._sc_scrollingSurface = SC.InternalListViewSurface.create({
+ supersurface: this,
+ __scrollView__: this
+ });
+
+ this._sc_scrollTranslation = SC.MakePoint();
+ this._sc_contentPropertyDidChange();
+ this._sc_selectionDidChange();
+ this._sc_plistItems = {};
+ },
+
+ rowOffsetForLayerTree: function(layerTree) {
+ console.log("SC.IListViewSurface#rowOffsetForLayerTree()");
+ var plistItems = this._sc_plistItems;
+ for (var storeKey in plistItems) {
+ if (!plistItems.hasOwnProperty(storeKey)) continue;
+ var plistItem = plistItems[storeKey];
+ var plistLayerTree = plistItem.editableLayerTree;
+ if (!plistLayerTree) plistLayerTree = plistItem.mouseLayerTree;
+ if (!plistLayerTree) plistLayerTree = plistItem.renderLayerTree;
+
+ if (plistLayerTree === layerTree) {
+ return plistItem.offset + parseInt(this._sc_context.__sc_canvas__.style.top.slice(0,-2), 10);
+ }
+ }
+ debugger;
+ console.log('Could not find row offset for layer tree. This is a bug.');
+ return 0; // Don't know!
+ }
+
+});
+
+/** @private */
+SC.InternalListViewSurface = SC.LeafSurface.extend({
+
+ __tagName__: 'div',
+
+ isLeafSurface: false,
+ isCompositeSurface: true, // Walk like a duck.
+ subsurfaces: function() {
+ return [this._sc_scrollingCanvas];
+ }.property(),
+
+ rowOffsetForLayerTree: function(layerTree) {
+ return this.__scrollView__.rowOffsetForLayerTree(layerTree);
+ },
+
+ // ..........................................................
+ // SURFACE TREE SUPPORT
+ //
+
+ _sc_compositeIsPresentInViewportDidChange: function() {
+ // console.log("SC.InternalListViewSurface#_sc_compositeIsPresentInViewportDidChange()");
+ var isPresentInViewport = this.get('isPresentInViewport');
+ this._sc_scrollingCanvas.set('isPresentInViewport', isPresentInViewport);
+ }.observes('isPresentInViewport'),
+
+ __scrollView__: null,
+
+ surface: function() {
+ // console.log('SC.InternalListViewSurface@surface');
+ return this.__scrollView__;
+ }.property().cacheable(),
+
+ didCreateElement: function(div) {
+ // console.log('SC.InternalListViewSurface#didCreateElement()', SC.guidFor(this));
+ arguments.callee.base.apply(this, arguments);
+ div.style.overflow = 'hidden';
+ },
+
+ targetResponderForEvent: function(evt) {
+ return this.get('surface').targetResponderForEvent(evt);
+ },
+
+ // performLayoutIfNeeded: function(timestamp) {
+ // console.log('SC.InternalListViewSurface#performLayoutIfNeeded()', SC.guidFor(this));
+ // arguments.callee.base.apply(this, arguments);
+ // // this._sc_scrollingCanvas.performLayoutIfNeeded(timestamp);
+ // },
+
+ // ..........................................................
+ // PSURFACE SUPPORT (Private)
+ //
+
+ updatePsurface: function(psurface, surfaces) {
+ // console.log('SC.InternalListViewSurface#updatePsurface()');
+
+ sc_assert(this === SC.surfaces[this.__id__], "SC.Surface#updatePsurface() can only be called on active surfaces.");
+
+ // Sanity check the Psurface.
+ sc_assert(psurface);
+ sc_assert(psurface instanceof SC.Psurface);
+ sc_assert(psurface.__element__);
+ sc_assert(psurface.__element__ === document.getElementById(this.__id__));
+
+ var surface = this._sc_scrollingCanvas;
+ if (surfaces) surfaces[surface.__id__] = surface;
+ psurface.push(surface);
+ },
+
+ updateLayout: function() {
+ // console.log('SC.InternalListViewSurface#updateLayout()', SC.guidFor(this));
+ arguments.callee.base.apply(this, arguments);
+
+ this.adjustLayout();
+ },
+
+ _sc_scrollingCanvas: null,
+
+ init: function() {
+ arguments.callee.base.apply(this, arguments);
+ var scrollingCanvas;
+ scrollingCanvas = this._sc_scrollingCanvas = SC.InternalListViewCanvas.create({
+ supersurface: this,
+ __scrollSurface__: this
+ });
+ },
+
+ adjustLayout: function() {
+ // console.log('SC.InternalListViewSurface#adjustLayout()', SC.guidFor(this));
+ var frame = SC.MakeRect(this.__scrollView__.get('frame')),
+ myFrame = SC.MakeRect(this.get('frame')),
+ rowHeight = this.__scrollView__.get('rowHeight');
+
+ frame[3] = frame[3] * 2;
+
+ // We need to be even with the rowHeight
+ frame[3] = frame[3] + (rowHeight - (frame[3] % rowHeight));
+
+ // Record how many rows we can render.
+ this.__scrollView__._sc_rowLength = frame[3] / rowHeight;
+
+ frame.x = myFrame.x;
+ frame.y = myFrame.y;
+ this._sc_scrollingCanvas.set('frame', frame);
+ }
+
+});
+
+/** @private */
+SC.InternalListViewCanvas = SC.LeafSurface.extend({
+
+ __tagName__: 'canvas',
+
+ __useContentSize__: true, // we need our width and height attributes set
+ __neverAnimate__: true,
+
+ __scrollSurface__: null,
+
+ surface: function() {
+ // console.log('SC.InternalListViewCanvas@surface');
+ return this.__scrollSurface__;
+ }.property().cacheable(),
+
+ didCreateElement: function(canvas) {
+ // console.log('SC.InternalListViewCanvas#didCreateElement()', SC.guidFor(this));
+ arguments.callee.base.apply(this, arguments);
+ var ctx = canvas.getContext('2d');
+
+ // Enables ctx.width and ctx.height to work.
+ ctx.__sc_canvas__ = canvas;
+
+ this.__scrollSurface__.__scrollView__._sc_context = ctx;
+ this.__scrollSurface__.__scrollView__.triggerRendering();
+ },
+
+ rowOffsetForLayerTree: function(layerTree) {
+ return this.__scrollSurface__.rowOffsetForLayerTree(layerTree);
+ },
+
+ // performLayoutIfNeeded: function(timestamp) {
+ // console.log('SC.InternalListViewCanvas#performLayoutIfNeeded()', SC.guidFor(this));
+ // arguments.callee.base.apply(this, arguments);
+ // },
+
+ // updateLayout: function() {
+ // console.log('SC.InternalListViewCanvas#updateLayout()', SC.guidFor(this));
+ // },
+
+ targetResponderForEvent: function(evt) {
+ return this.get('surface').targetResponderForEvent(evt);
+ }
+
+});
+
+// Constructor
+SC.PListItem = function(index, object, offset) {
+ // Set all properties for speed with hidden classes.
+ this.index = index;
+ this.object = object;
+ this.storeKey = object.storeKey;
+ this.offset = offset;
+ this.isSelected = false;
+ this.isLast = false;
+ this.needsRendering = true;
+ this.dependentStoreKeys = null;
+
+ // A PListItem can only have one render function or layer tree, not both.
+ this.renderFunction = null; // Shared with other PListItems
+ this.renderLayerTree = null; // Shared with other PListItems
+
+ // A PListItem can only have mouse or editable, not both.
+ this.mouseLayerTree = null; // Exclusive to this PListItem
+ this.editableLayerTree = null; // Exclusive to this PListItem
+
+ return this;
+};
View
12 application/surfaces/list_view.js
@@ -3,7 +3,7 @@
// Copyright: ©2012 Fohr Motion Picture Studios. All rights reserved.
// License: Licensed under the GPLv3 license (see BLOSSOM-LICENSE).
// ==========================================================================
-/*globals sc_assert ENFORCE_BLOSSOM_2DCONTEXT_API */
+/*globals sc_assert */
sc_require('surfaces/scroll_view');
@@ -64,6 +64,9 @@ SC.ListView = SC.ScrollView.extend({
context.fillText(String(index), width/2, height/2);
},
+ hasHorizontalScroller: false,
+ clearBackground: true,
+
updateDisplay: function() {
// console.log('SC.ListView#updateDisplay()', SC.guidFor(this));
var benchKey = 'SC.ListView#updateDisplay()';
@@ -91,7 +94,11 @@ SC.ListView = SC.ScrollView.extend({
var obj = content.objectAt(idx);
ctx.save();
ctx.translate(0, idx*h);
- this.renderRow(ctx, w, h, idx, obj, selection.contains(obj));
+ if (this.renderRow) {
+ this.renderRow(ctx, w, h, idx, obj, selection.contains(obj),
+ idx===0? true : false,
+ idx===(len-1)? true : false);
+ }
ctx.restore();
}
}
@@ -115,6 +122,7 @@ SC.ListView = SC.ScrollView.extend({
return true;
},
+ // FIXME: This behavior is only needed on touch devices!
mouseUp: function(evt) {
var idx = this._rowIndex,
content = this._sc_content,
View
23 application/surfaces/private/psurface.js
@@ -100,6 +100,7 @@ SC.Psurface = function(surface) {
element.id = surfaceId;
if (DEBUG_PSURFACES) sc_assert(element, "Failed to create element with tagName"+(tagName || 'div'));
this.__element__ = element;
+ this.animate = !surface.__neverAnimate__;
style.position = 'absolute';
// Prevent user-resizing of text areas.
@@ -308,11 +309,13 @@ SC.Psurface.prototype = {
var transition = currentHash[key];
if (transition) {
if (key === 'frame') {
- properties.push('left', 'top', 'width', 'height');
- for (idx=0, len=4; idx<len; ++idx) {
- durations.push(transition.duration+'ms');
- timingFunctions.push(transition.timingFunction);
- delays.push(transition.delay+'ms');
+ if (this.animate) {
+ properties.push('left', 'top', 'width', 'height');
+ for (idx=0, len=4; idx<len; ++idx) {
+ durations.push(transition.duration+'ms');
+ timingFunctions.push(transition.timingFunction);
+ delays.push(transition.delay+'ms');
+ }
}
value = transition.value;
style.left = value[0]/*x*/ + 'px';
@@ -380,10 +383,12 @@ SC.Psurface.prototype = {
}
} else { // The remaining properties are set directly.
- properties.push(transition.cssKey);
- durations.push(transition.duration+'ms');
- timingFunctions.push(transition.timingFunction);
- delays.push(transition.delay+'ms');
+ if (this.animate) {
+ properties.push(transition.cssKey);
+ durations.push(transition.duration+'ms');
+ timingFunctions.push(transition.timingFunction);
+ delays.push(transition.delay+'ms');
+ }
style[transition.cssKey] = transition.value;
}
View
21 application/surfaces/scroll_view.js
@@ -3,7 +3,7 @@
// Copyright: ©2012 Fohr Motion Picture Studios. All rights reserved.
// License: Licensed under the GPLv3 license (see BLOSSOM-LICENSE).
// ==========================================================================
-/*globals sc_assert ENFORCE_BLOSSOM_2DCONTEXT_API */
+/*globals sc_assert */
sc_require('surfaces/view');
@@ -83,6 +83,20 @@ SC.ScrollView = SC.View.extend({
// We don't want SC.View's implementation; don't call it.
div.style.overflowX = this.get('hasHorizontalScroller')? 'scroll' : 'hidden';
div.style.overflowY = this.get('hasVerticalScroller')? 'scroll' : 'hidden';
+
+ // FIXME: This should be done dynamically, per scrollview. I'm not doing
+ // it now because the CSS has pseudo-selectors, so I have to generate
+ // stylesheet code specially. (Here and a few other places, actually.)
+ //
+ // For now, I'll specially customize the CSS to work with Postbooks' UI
+ // correctly.
+ div.className = 'frame';
+
+ // This should probably only be set on mobile Safari/Google Chrome for
+ // Android.
+ //
+ // See http://stackoverflow.com/questions/7763458/ios5-webkit-overflow-scrolling-causes-touch-events-stopping-work
+ // for a fix I haven't yet implemented, too.
div.style.webkitOverflowScrolling = 'touch';
},
@@ -163,9 +177,12 @@ SC.InternalScrollViewCanvas = SC.LeafSurface.extend({
// Enables ctx.width and ctx.height to work.
ctx.__sc_canvas__ = canvas;
- if (ENFORCE_BLOSSOM_2DCONTEXT_API) delete ctx.canvas;
this.__scrollView__._sc_context = ctx;
this.__scrollView__.triggerRendering();
+ },
+
+ targetResponderForEvent: function(evt) {
+ return this.get('surface').targetResponderForEvent(evt);
}
});
View
3 application/surfaces/split.js
@@ -3,7 +3,7 @@
// Copyright: ©2012 Fohr Motion Picture Studios. All rights reserved.
// License: Licensed under the GPLv3 license (see BLOSSOM-LICENSE).
// ==========================================================================
-/*globals sc_assert ENFORCE_BLOSSOM_2DCONTEXT_API */
+/*globals sc_assert */
sc_require('surfaces/composite');
sc_require('surfaces/view');
@@ -740,7 +740,6 @@ SC.SplitSurface = SC.CompositeSurface.extend(SC.DelegateSupport,
// Enables ctx.width and ctx.height to work.
ctx.__sc_canvas__ = canvas;
- if (ENFORCE_BLOSSOM_2DCONTEXT_API) delete ctx.canvas;
that._sc_dividerContext = ctx;
},
View
6 application/surfaces/surface.js
@@ -124,7 +124,7 @@ SC.animatablePropertyBuilder = function(key, assertion) {
@extends SC.Responder
@since Blossom 1.0
*/
-SC.Surface = SC.Responder.extend({
+SC.Surface = SC.Object.extend(SC.Responder, {
isSurface: true,
isResponderContext: true, // We can dispatch to other responders.
@@ -646,6 +646,8 @@ SC.Surface = SC.Responder.extend({
__useContentSize__: false,
+ __neverAnimate__: false,
+
// These only apply to leaf surfaces, but I'm putting them here because the
// frame method is what updates them.
__contentWidth__: 0,
@@ -730,6 +732,8 @@ SC.Surface = SC.Responder.extend({
style[cssKey] = value;
}
}
+ //
+ // style.overflow = 'auto';
},
// ..........................................................
View
381 application/surfaces/table_view.js
@@ -0,0 +1,381 @@
+// ==========================================================================
+// Project: Blossom - Modern, Cross-Platform Application Framework
+// Copyright: ©2012 Fohr Motion Picture Studios. All rights reserved.
+// License: Licensed under the GPLv3 license (see BLOSSOM-LICENSE).
+// ==========================================================================
+/*globals sc_assert */
+
+sc_require('surfaces/layout');
+sc_require('surfaces/ilist_view');
+sc_require('layers/label');
+
+var base03 = "#002b36";
+var base02 = "#073642";
+var base01 = "#586e75";
+var base00 = "#657b83";
+var base0 = "#839496";
+var base1 = "#93a1a1";
+var base2 = "#eee8d5";
+var base3 = "#fdf6e3";
+var yellow = "#b58900";
+var orange = "#cb4b16";
+var red = "#dc322f";
+var magenta = "#d33682";
+var violet = "#6c71c4";
+var blue = "#268bd2";
+var cyan = "#2aa198";
+var green = "#859900";
+var white = "white";
+var black = "black";
+
+SC.TableView = SC.LayoutSurface.extend({
+
+ columns: [],
+
+ // Proxies for SC.IListView
+
+ hasHorizontalScroller: false,
+ hasVerticalScroller: true,
+
+ content: null,
+ contentBindingDefault: SC.Binding.multiple(),
+
+ selection: null,
+ rowHeight: 30,
+
+ init: function() {
+ arguments.callee.base.apply(this, arguments);
+ var columns = this.get('columns');
+
+ var header = SC.View.create({
+ layout: { top: 0, left: 0, right: 0, height: 24 }
+ });
+
+ var accumulatedWidth = 0,
+ layers = header.get('layers');
+
+ columns.forEach(function(column) {
+ sc_assert(column.kindOf(SC.TableColumn));
+
+ var widget = SC.TableHeaderWidget.create({
+ layout: { top: 0, left: accumulatedWidth, width: column.get('width'), bottom: 0 },
+ column: column
+ });
+
+ layers.pushObject(widget);
+ accumulatedWidth += column.get('width');
+ }, this);
+
+ this.get('subsurfaces').pushObject(header);
+
+ var table = SC.TableIListView.create({
+ columns: columns,
+
+ contentBinding: SC.Binding.from('content', this).noDelay(),
+ selectionBinding: SC.Binding.from('selection', this).noDelay(),
+
+ hasHorizontalScrollerBinding: SC.Binding.from('hasHorizontalScroller', this).noDelay(),
+ hasVerticalScrollerBinding: SC.Binding.from('hasVerticalScroller', this).noDelay(),
+
+ rowHeightBinding: SC.Binding.from('rowHeight', this).noDelay(),
+
+ renderRow: function(context, width, height, index, object, isSelected) {
+ context.fillStyle = isSelected? '#99CCFF' : 'white';
+ context.fillRect(0, 0, width, height);
+
+ context.strokeStyle = 'grey';
+ context.lineWidth = 1;
+
+ context.beginPath();
+ context.moveTo(0, height - 0.5);
+ context.lineTo(width, height - 0.5);
+ context.stroke();
+
+ context.font = "12pt Helvetica";
+ context.fillStyle = 'black';
+ context.textAlign = 'center';
+ context.textBaseline = 'middle';
+
+ context.fillText(String(index), width/2, height/2);
+ }
+ });
+ table.set('backgroundColor', 'grey');
+
+ this.get('subsurfaces').pushObject(table);
+ }
+
+});
+
+SC.TableIListView = SC.IListView.extend({
+ layout: { bottom: 0, left: 0, right: 0, top: 24 },
+
+ columns: null,
+
+ // createRenderLayerTree: function() {
+ // // console.log('creating render tree');
+ // // return SC.Layer.create({
+ // // render: function(ctx) {
+ // // var benchKey = 'render';
+ // // SC.Benchmark.start(benchKey);
+ // // var bounds = this.get('bounds'),
+ // // content = this.get('content'),
+ // // title = content.get('name');
+ // //
+ // // ctx.fillStyle = 'grey';
+ // // ctx.fillRect(0,0,bounds.w,bounds.h);
+ // //
+ // // // Draw the text.
+ // // ctx.textBaseline = 'middle';
+ // // ctx.font = '12pt Helvetica';
+ // // ctx.fillStyle = 'white';
+ // // ctx.fillText(title, 4, 3);
+ // // SC.Benchmark.end(benchKey);
+ // // }
+ // // });
+ // var benchKey = 'SC.TableIListView#createRenderLayerTree()';
+ // SC.Benchmark.start(benchKey);
+ // var root = SC.Layer.create(),
+ // layers = root.get('sublayers');
+ //
+ //
+ // var columns = this.get('columns'),
+ // accumulatedWidth = 0;
+ //
+ // columns.forEach(function(column) {
+ // sc_assert(column.kindOf(SC.TableColumn));
+ //
+ // var label = SC.TableViewCell.create({
+ // layout: { top: 1, left: accumulatedWidth, width: column.get('width')-1, bottom: 0 },
+ //
+ // valueBinding: SC.Binding.from('*content.'+column.get('key')).noDelay()
+ // });
+ //
+ // layers.pushObject(label);
+ // accumulatedWidth += column.get('width');
+ // }, this);
+ //
+ // SC.Benchmark.end(benchKey);
+ // return root;
+ // }
+
+});
+
+SC.TableViewCell = SC.Layer.extend({
+
+ displayProperties: 'value'.w(),
+
+ // FIXME: Add more text properties.
+ font: "10pt Helvetica, sans",
+ color: base03,
+ backgroundColor: base3,
+ textBaseline: 'top',