Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Quite messy and hacky in some areas at the moment; not so bad in othe…

…rs. Yay.
  • Loading branch information...
commit 6a15918fb23dc317fe09fdcd365cdf49873ee514 0 parents
@ialexi ialexi authored
Showing with 2,506 additions and 0 deletions.
  1. +2 −0  .gitignore
  2. +3 −0  .gitmodules
  3. +8 −0 Buildfile
  4. +7 −0 README
  5. +30 −0 apps/hedwig/controllers/article.js
  6. +46 −0 apps/hedwig/controllers/demo.js
  7. +42 −0 apps/hedwig/controllers/guide.js
  8. +67 −0 apps/hedwig/controllers/guide_browser.js
  9. +30 −0 apps/hedwig/controllers/guides.js
  10. +27 −0 apps/hedwig/core.js
  11. +19 −0 apps/hedwig/main.js
  12. +26 −0 apps/hedwig/resources/guide/touch.guide
  13. +1 −0  apps/hedwig/resources/guide/touch.json
  14. +9 −0 apps/hedwig/resources/loading.rhtml
  15. +118 −0 apps/hedwig/resources/main_page.js
  16. 0  apps/hedwig/resources/style.css
  17. +11 −0 apps/hedwig/responders/DEMO.js
  18. +18 −0 apps/hedwig/responders/NORMAL.js
  19. +8 −0 apps/hedwig/responders/SOURCE.js
  20. +8 −0 apps/hedwig/responders/START.js
  21. +15 −0 apps/hedwig/tests/controllers/article.js
  22. +15 −0 apps/hedwig/tests/controllers/demo.js
  23. +15 −0 apps/hedwig/tests/controllers/guide.js
  24. +15 −0 apps/hedwig/tests/controllers/guide_browser.js
  25. +15 −0 apps/hedwig/tests/controllers/guides.js
  26. +15 −0 apps/hedwig/tests/views/demo_panel.js
  27. +41 −0 apps/hedwig/views/demo_panel.js
  28. +102 −0 docs/.json
  29. +98 −0 docs/build/articles/touch/a-brief-touch.html
  30. +1 −0  docs/build/articles/touch/a-brief-touch.json
  31. +15 −0 docs/build/articles/touch/a-brief-touch.md
  32. +8 −0 docs/build/articles/touch/touch-demo.js
  33. +92 −0 docs/build/articles/touch/touch-events.html
  34. +1 −0  docs/build/articles/touch/touch-events.json
  35. 0  docs/build/articles/touch/touch-events.md
  36. +26 −0 docs/build/guides/touch.guide
  37. +1 −0  docs/build/guides/touch.json
  38. +98 −0 docs/build/template.haml
  39. +1 −0  docs/hoo
  40. +15 −0 docs/src/articles/touch/a-brief-touch.md
  41. +8 −0 docs/src/articles/touch/touch-demo.js
  42. 0  docs/src/articles/touch/touch-events.md
  43. +26 −0 docs/src/guides/touch.guide
  44. +98 −0 docs/src/template.haml
  45. +98 −0 docs/template.haml
  46. +1 −0  frameworks/sproutcore
  47. +1 −0  themes/pig/Buildfile
  48. +2 −0  themes/pig/build
  49. +2 −0  themes/pig/build-d
  50. +113 −0 themes/pig/chance/README.md
  51. +173 −0 themes/pig/chance/chance.rb
  52. +202 −0 themes/pig/chance/lib/css.rb
  53. +388 −0 themes/pig/chance/lib/slicedice.rb
  54. 0  themes/pig/chance/sc-theme.rb
  55. +126 −0 themes/pig/chance/scripts/setup.rb
  56. +4 −0 themes/pig/pig.js
  57. BIN  themes/pig/resources/images/1.png
  58. BIN  themes/pig/resources/images/2.png
  59. +59 −0 themes/pig/resources/theme.css
  60. +3 −0  themes/pig/src/master-detail/master-detail.css
  61. +7 −0 themes/pig/src/master-detail/master-detail.js
  62. +15 −0 themes/pig/src/paper/paper.css
  63. +3 −0  themes/pig/src/paper/paper.js
  64. +38 −0 themes/pig/src/paper/toolbar/buttons.css
  65. +15 −0 themes/pig/src/paper/toolbar/chromeless.js
  66. +27 −0 themes/pig/src/paper/toolbar/icon.js
  67. BIN  themes/pig/src/paper/toolbar/next.png
  68. BIN  themes/pig/src/paper/toolbar/next.psd
  69. BIN  themes/pig/src/paper/toolbar/previous.png
  70. BIN  themes/pig/src/paper/toolbar/previous.psd
  71. +4 −0 themes/pig/src/paper/toolbar/toolbar.css
  72. +3 −0  themes/pig/src/static-content.css
  73. BIN  themes/pig/src/tree/bg.png
  74. +21 −0 themes/pig/src/tree/tree.css
2  .gitignore
@@ -0,0 +1,2 @@
+tmp/
+.DS_Store
3  .gitmodules
@@ -0,0 +1,3 @@
+[submodule "frameworks/sproutcore"]
+ path = frameworks/sproutcore
+ url = git://github.com/sproutit/sproutcore.git
8 Buildfile
@@ -0,0 +1,8 @@
+# ===========================================================================
+# Project: Hedwig
+# Copyright: ©2010 My Company, Inc.
+# ===========================================================================
+
+# Add initial buildfile information here
+config :all, :required => :sproutcore, :theme=>:pig, :url_prefix => "/static/suite/",
+ :html5_manifest=> true
7 README
@@ -0,0 +1,7 @@
+=============================================================================
+Project: Hedwig
+Copyright: ©2010 My Company, Inc.
+=============================================================================
+
+TODO: Describe Your Project
+
30 apps/hedwig/controllers/article.js
@@ -0,0 +1,30 @@
+// ==========================================================================
+// Project: Hedwig.articleController
+// Copyright: ©2010 My Company, Inc.
+// ==========================================================================
+/*globals Hedwig */
+
+/** @class
+
+ (Document Your Controller Here)
+
+ @extends SC.Object
+*/
+Hedwig.articleController = SC.ObjectController.create(
+/** @scope Hedwig.articleController.prototype */ {
+ contentBinding: "Hedwig.guideBrowserController.selection",
+ contentBindingDefault: SC.Binding.single(),
+
+ demoFor: function(href) {
+ if (this.get("demos")) return this.get("demos")[href];
+ return "";
+ },
+
+ replacementFor: function(href) {
+ return "<div class='hedwig-demo' href='" + href + "' style='width:300px;height:200px;background-color:red;'></div>";
+ },
+
+ openDemo: function(href) {
+ console.error("DEMO: " + href);
+ }
+}) ;
46 apps/hedwig/controllers/demo.js
@@ -0,0 +1,46 @@
+// ==========================================================================
+// Project: Hedwig.demoController
+// Copyright: ©2010 My Company, Inc.
+// ==========================================================================
+/*globals Hedwig */
+/*jslint evil:true */
+
+/** @class
+
+ (Document Your Controller Here)
+
+ @extends SC.Object
+*/
+Hedwig.demoController = SC.ObjectController.create(
+/** @scope Hedwig.demoController.prototype */ {
+
+ demo: null,
+ demoSource: function() {
+ return Hedwig.articleController.demoFor(this.get("demo"));
+ }.property("demo").cacheable(),
+
+ openDemo: function() {
+ try {
+ var source = this.get("demoSource");
+
+ //err... yes, this very badly immitates CommonJS modules. Sorry. :(
+ var obj = {};
+ var res = function (exports) {
+ // load module
+ eval(source);
+ }(obj);
+
+ var demoPanel = Hedwig.DemoPanel.generateWithView(obj.getDemoView());
+ demoPanel.append();
+ this._openDemoPanel = demoPanel;
+
+ } catch (e) {
+
+ }
+ },
+
+ closeDemo: function() {
+ this._openDemoPanel.remove();
+ }
+
+}) ;
42 apps/hedwig/controllers/guide.js
@@ -0,0 +1,42 @@
+// ==========================================================================
+// Project: Hedwig.guideController
+// Copyright: ©2010 My Company, Inc.
+// ==========================================================================
+/*globals Hedwig */
+
+/** @class
+
+ (Document Your Controller Here)
+
+ @extends SC.Object
+*/
+Hedwig.guideController = SC.ObjectController.create(
+/** @scope Hedwig.guideController.prototype */ {
+ contentBinding: "Hedwig.guidesController.currentGuide",
+
+ /**
+ This builds a normalized tree structure usable by a TreeController.
+ We do this because the guide format is in "pretty" JSON-- that is, its format makes sense to read.
+ Property names such as "children" are not as friendly as "sections".
+ */
+ guideTree: function() {
+ var content = this.get("content");
+ if (!content) return SC.Object.create();
+
+ var ret = SC.Object.create(content);
+ ret.set("treeItemIsExpanded", YES);
+
+ ret.set("treeItemChildren", content.sections.map(function(section){
+ var ret = SC.Object.create(section);
+ ret.set("treeItemIsExpanded", YES);
+ ret.set("treeItemChildren", section.articles.map(function(article){
+ var ret = SC.Object.create(article);
+ ret.set("contents", ret.get("content")); // whoops. Preprocessor FAIL. It should have named it "contents," huh?
+ return ret;
+ }));
+ return ret;
+ }));
+
+ return ret;
+ }.property("content").cacheable()
+}) ;
67 apps/hedwig/controllers/guide_browser.js
@@ -0,0 +1,67 @@
+// ==========================================================================
+// Project: Hedwig.guideBrowserController
+// Copyright: ©2010 My Company, Inc.
+// ==========================================================================
+/*globals Hedwig */
+
+/** @class
+
+ (Document Your Controller Here)
+
+ @extends SC.Object
+*/
+Hedwig.guideBrowserController = SC.TreeController.create(
+/** @scope Hedwig.guideBrowserController.prototype */ {
+ contentBinding: "Hedwig.guideController.guideTree",
+
+
+ treeItemChildrenKey: "treeItemChildren",
+ treeItemIsGrouped: YES,
+ treeItemIsExpandedKey: "treeItemIsExpanded",
+
+ allowsMultipleSelection: NO,
+ allowsEmptySelection: NO,
+
+ _getSelectionIndexSet: function(){
+ var ao = this.get("arrangedObjects");
+ return this.get("selection").indexSetForSource(ao);
+ },
+
+ hasPreviousArticle: function() {
+ return !!this.get("previousArticle");
+ }.property("previousArticle").cacheable(),
+
+ hasNextArticle: function() {
+ return !!this.get("nextArticle");
+ }.property("nextArticle").cacheable(),
+
+ previousArticle: function() {
+ var ao = this.get("arrangedObjects"), set = this.get("selection").indexSetForSource(ao);
+ if (!set) return NO;
+
+ var first = set.get("min");
+ var indexes = ao.contentGroupIndexes(null, ao);
+
+ // now start trying indexes
+ var idx = first - 1;
+ for (; idx >= 0; idx--) {
+ if (!indexes.contains(idx)) return ao.objectAt(idx);
+ }
+ return null;
+ }.property("selection", "arrangedObjects").cacheable(),
+
+ nextArticle: function(){
+ var ao = this.get("arrangedObjects"), set = this.get("selection").indexSetForSource(ao);
+ if (!set) return;
+
+ var last = set.get("max");
+ var indexes = ao.contentGroupIndexes(null, ao);
+
+ // now start trying indexes
+ var idx = last, len = ao.get("length");
+ for (; idx < len; idx++) {
+ if (!indexes.contains(idx)) return ao.objectAt(idx);
+ }
+ return null;
+ }.property("arrangedObjects", "selection").cacheable()
+}) ;
30 apps/hedwig/controllers/guides.js
@@ -0,0 +1,30 @@
+// ==========================================================================
+// Project: Hedwig.guidesController
+// Copyright: ©2010 My Company, Inc.
+// ==========================================================================
+/*globals Hedwig */
+
+/** @class
+
+ (Document Your Controller Here)
+
+ @extends SC.Object
+*/
+// for now, we are hard-coding the guide in...
+Hedwig.guidesController = SC.ObjectController.create(
+/** @scope Hedwig.guidesController.prototype */ {
+
+ currentGuide: null,
+ loadGuide: function(path) {
+ console.error(path);
+ SC.Request.getUrl(path).json().notify(this, "didLoadGuide").send();
+ },
+
+ didLoadGuide: function(response) {
+ // obviously I have no error handling right now. Did I mention I'm in a bit of a hurry?
+ // /me crosses fingers
+ if (SC.ok(response)) {
+ this.set("currentGuide", response.get("body"));
+ }
+ }
+}) ;
27 apps/hedwig/core.js
@@ -0,0 +1,27 @@
+// ==========================================================================
+// Project: Hedwig
+// Copyright: ©2010 My Company, Inc.
+// ==========================================================================
+/*globals Hedwig */
+SC.LOG_TOUCH_EVENTS = YES;
+/** @namespace
+
+ My cool new app. Describe your application.
+
+ @extends SC.Object
+*/
+Hedwig = SC.Application.create(
+ /** @scope Hedwig.prototype */ {
+
+ NAMESPACE: 'Hedwig',
+ VERSION: '0.1.0',
+
+ // This is your application store. You will use this store to access all
+ // of your model data. You can also set a data source on this store to
+ // connect to a backend server. The default setup below connects the store
+ // to any fixtures you define.
+ store: SC.Store.create().from(SC.Record.fixtures)
+
+ // TODO: Add global constants or singleton objects needed by your app here.
+
+}) ;
19 apps/hedwig/main.js
@@ -0,0 +1,19 @@
+// ==========================================================================
+// Project: Hedwig
+// Copyright: ©2010 My Company, Inc.
+// ==========================================================================
+/*globals Hedwig */
+
+// This is the function that will start your app running. The default
+// implementation will load any fixtures you have created then instantiate
+// your controllers and awake the elements on your page.
+//
+// As you develop your application you will probably want to override this.
+// See comments for some pointers on what to do next.
+//
+Hedwig.main = function main() {
+ Hedwig.makeFirstResponder(Hedwig.START);
+
+} ;
+
+function main() { Hedwig.main(); }
26 apps/hedwig/resources/guide/touch.guide
@@ -0,0 +1,26 @@
+{
+ "title": "Writing Touch Applications",
+ "sections": [
+ {
+ "title": "Introduction",
+ "articles": [
+ "articles/touch/a-brief-touch",
+ "articles/touch/touch-events"
+ ]
+ },
+ {
+ "title": "Built-In Support",
+ "articles": [
+ "articles/controls/button/touch",
+ "articles/controls/scroll/touch"
+ ]
+ },
+ {
+ "title": "Advanced Concepts",
+ "articles": [
+ "articles/touch/capturing",
+ "articles/touch/releasing"
+ ]
+ }
+ ]
+}
1  apps/hedwig/resources/guide/touch.json
@@ -0,0 +1 @@
+{"title":"Writing Touch Applications","sections":[{"title":"Introduction","articles":[{"content":"<h1>A Brief Touch on Touches</h1>\n\n<p>Some text goes here.</p>\n\n<p>Note that links like the below must appear on empty lines with no preceding whitespace.</p>\n\n<p><a href='touch-demo.js' class='demo'>touch-demo.js</a></p>","errors":[],"demos":{"touch-demo.js":"var MyExampleView = SC.View.extend({\n backgroundColor: \"green\"\n});\n\n// bootstrap code :)\nexports.getDemoView = function() {\n return MyExampleView;\n};"},"articleDirectory":"articles/touch/","outputDirectory":"build/","title":"A Brief Touch on Touches","any":"metadata","goes":"Here","damn":"gruber","this":"is still eye-readable","and":"He is wrong about touch apps."},{"content":"","errors":[],"demos":{},"articleDirectory":"articles/touch/","outputDirectory":"build/"}]},{"title":"Built-In Support","articles":[{"error":{"message":"No such file or directory","stack":"Error: No such file or directory\n at Object.readFileSync (fs:74:20)\n at Object.readFile (/Users/alex/.seeds/packages/core-support/0.2.1/lib/fs.js:348:37)\n at /Volumes/Docs/Work/web/hedwig/docs/hoo/lib/guide.js:9:27\n at /Volumes/Docs/Work/web/hedwig/docs/hoo/lib/guide.js:22:48\n at Object.process (/Volumes/Docs/Work/web/hedwig/docs/hoo/lib/guide.js:30:38)\n at /Volumes/Docs/Work/web/hedwig/docs/hoo/bin/hoo-worker:55:17\n at Stream.<anonymous> (/Volumes/Docs/Work/web/hedwig/docs/hoo/packages/spawn/lib/spawn.js:63:25)\n at IOWatcher.callback (net:310:14)\n at node.js:818:9","errno":2},"file":"build/articles/controls/button/touch.json"},{"error":{"message":"No such file or directory","stack":"Error: No such file or directory\n at Object.readFileSync (fs:74:20)\n at Object.readFile (/Users/alex/.seeds/packages/core-support/0.2.1/lib/fs.js:348:37)\n at /Volumes/Docs/Work/web/hedwig/docs/hoo/lib/guide.js:9:27\n at /Volumes/Docs/Work/web/hedwig/docs/hoo/lib/guide.js:22:48\n at Object.process (/Volumes/Docs/Work/web/hedwig/docs/hoo/lib/guide.js:30:38)\n at /Volumes/Docs/Work/web/hedwig/docs/hoo/bin/hoo-worker:55:17\n at Stream.<anonymous> (/Volumes/Docs/Work/web/hedwig/docs/hoo/packages/spawn/lib/spawn.js:63:25)\n at IOWatcher.callback (net:310:14)\n at node.js:818:9","errno":2},"file":"build/articles/controls/scroll/touch.json"}]},{"title":"Advanced Concepts","articles":[{"error":{"message":"No such file or directory","stack":"Error: No such file or directory\n at Object.readFileSync (fs:74:20)\n at Object.readFile (/Users/alex/.seeds/packages/core-support/0.2.1/lib/fs.js:348:37)\n at /Volumes/Docs/Work/web/hedwig/docs/hoo/lib/guide.js:9:27\n at /Volumes/Docs/Work/web/hedwig/docs/hoo/lib/guide.js:22:48\n at Object.process (/Volumes/Docs/Work/web/hedwig/docs/hoo/lib/guide.js:30:38)\n at /Volumes/Docs/Work/web/hedwig/docs/hoo/bin/hoo-worker:55:17\n at Stream.<anonymous> (/Volumes/Docs/Work/web/hedwig/docs/hoo/packages/spawn/lib/spawn.js:63:25)\n at IOWatcher.callback (net:310:14)\n at node.js:818:9","errno":2},"file":"build/articles/touch/capturing.json"},{"error":{"message":"No such file or directory","stack":"Error: No such file or directory\n at Object.readFileSync (fs:74:20)\n at Object.readFile (/Users/alex/.seeds/packages/core-support/0.2.1/lib/fs.js:348:37)\n at /Volumes/Docs/Work/web/hedwig/docs/hoo/lib/guide.js:9:27\n at /Volumes/Docs/Work/web/hedwig/docs/hoo/lib/guide.js:22:48\n at Object.process (/Volumes/Docs/Work/web/hedwig/docs/hoo/lib/guide.js:30:38)\n at /Volumes/Docs/Work/web/hedwig/docs/hoo/bin/hoo-worker:55:17\n at Stream.<anonymous> (/Volumes/Docs/Work/web/hedwig/docs/hoo/packages/spawn/lib/spawn.js:63:25)\n at IOWatcher.callback (net:310:14)\n at node.js:818:9","errno":2},"file":"build/articles/touch/releasing.json"}]}],"file":"build/guides/touch"}
9 apps/hedwig/resources/loading.rhtml
@@ -0,0 +1,9 @@
+<% content_for :loading do %>
+<% # Any HTML in this file will be visible on screen while your page loads
+ # its application JavaScript. SproutCore applications are optimized for
+ # caching and startup very fast, so your users will often only see this
+ # content for a brief moment on their first app load, if at all.
+%>
+<p class="loading">Loading...<p>
+
+<% end %>
118 apps/hedwig/resources/main_page.js
@@ -0,0 +1,118 @@
+// ==========================================================================
+// Project: Hedwig - mainPage
+// Copyright: ©2010 My Company, Inc.
+// ==========================================================================
+/*globals Hedwig */
+/*
+ Hedwig UI
+ ---------
+ In short, we've got a standard iPad style two-up layout. In this case, we have
+ a MasterDetailView containing the left side and the right side.
+*/
+Hedwig.guidePath = sc_static("guide/touch.json");
+
+Hedwig.mainPage = SC.Page.design({
+
+ mainPane: SC.MainPane.design({
+ defaultResponder: Hedwig.NORMAL,
+
+ theme: "pig",
+ childViews: 'docs'.w(),
+
+ docs: SC.MasterDetailView.design({
+ autoHideMaster: YES,
+
+ // have to use workspace for master because it renders the popover (that we do need)
+ masterView: SC.WorkspaceView.design({
+ topToolbar: null,
+ contentView: SC.ScrollView.design({
+ contentView: SC.SourceListView.design({
+ classNames: ["desk"] ,
+ contentBinding: "Hedwig.guideBrowserController.arrangedObjects",
+ selectionBinding: "Hedwig.guideBrowserController.selection",
+ contentValueKey: "title"
+ })
+ })
+ }),
+
+ detailView: SC.WorkspaceView.design({
+ theme: "paper",
+ topToolbar: SC.ToolbarView.design(SC.FlowedLayout, {
+ autoResize: NO,
+ flowPadding: {top:0,bottom:0,right:10,left:10},
+ childViews: "title previous guide space next".w(),
+ previous: SC.ButtonView.design({
+ layout: { width: 44, height: 44 },
+ theme: "icon",
+ icon: "previous",
+ isEnabled: NO,
+ isEnabledBinding: "Hedwig.guideBrowserController.hasPreviousArticle",
+ action: "previousArticle"
+ }),
+ guide: SC.ButtonView.design({
+ layout: { width: 100, height: 44 },
+ title: "Guide",
+ theme: "chromeless",
+ isVisible: NO,
+ action: "toggleMasterPicker",
+ isVisibleBinding: ".parentView.masterIsHidden"
+ }),
+ space: SC.View.design({ isSpacer:YES }),
+ next: SC.ButtonView.design({
+ layout: { width: 44, height: 44 },
+ theme: "icon",
+ icon: "next",
+ action: "nextArticle",
+ isEnabled: NO,
+ isEnabledBinding: "Hedwig.guideBrowserController.hasNextArticle"
+ }),
+
+ title: SC.LabelView.design({
+ useAbsoluteLayout: YES,
+ layout: { left: 0, right: 0, centerY: 0, height: 20 },
+ textAlign: SC.ALIGN_CENTER,
+ valueBinding: "Hedwig.articleController.title"
+ })
+
+ }),
+
+ contentView: SC.ScrollView.design({
+ borderStyle: SC.BORDER_NONE,
+ contentView: SC.StaticContentView.design({
+ contentBinding: "Hedwig.articleController.contents",
+ contentDidChange: function() {
+ sc_super();
+ this.invokeLater("processContent", 1);
+ }.observes("content"),
+
+ processContent: function() {
+ var d = this.$("a.demo").forEach(function(x) {
+ x.outerHTML = Hedwig.articleController.replacementFor(x.getAttribute("href"));
+ }, this);
+ },
+ touchStart: function(touch) { this.mouseDown(touch); },
+ mouseDown: function(evt) {
+ evt.preventDefault();
+
+ var el = document.elementFromPoint(evt.pageX, evt.pageY), demoNode = null;
+ while (el) {
+ if (SC.$(el).hasClass("sc-view")) break;
+ if (SC.$(el).hasClass("hedwig-demo")) {
+ demoNode = el;
+ break;
+ }
+ el = el.parentNode;
+ }
+
+ if (demoNode) {
+ Hedwig.sendAction("openDemo", demoNode.getAttribute("href"));
+ }
+ }
+ })
+ })
+ })
+
+ })
+ })
+
+});
0  apps/hedwig/resources/style.css
No changes.
11 apps/hedwig/responders/DEMO.js
@@ -0,0 +1,11 @@
+/*global Hedwig */
+Hedwig.DEMO = SC.Responder.create({
+ didBecomeFirstResponder: function() {
+ Hedwig.demoController.openDemo();
+ },
+
+ closeDemo: function() {
+ Hedwig.demoController.closeDemo();
+ Hedwig.makeFirstResponder(Hedwig.NORMAL);
+ }
+});
18 apps/hedwig/responders/NORMAL.js
@@ -0,0 +1,18 @@
+/*global Hedwig */
+Hedwig.NORMAL = SC.Responder.create({
+ didBecomeFirstResponder: function() {
+ },
+
+ openDemo: function(demo) {
+ Hedwig.demoController.set("demo", demo);
+ Hedwig.makeFirstResponder(Hedwig.DEMO);
+ },
+
+ nextArticle: function() {
+ Hedwig.guideBrowserController.selectObject(Hedwig.guideBrowserController.get("nextArticle"));
+ },
+
+ previousArticle: function() {
+ Hedwig.guideBrowserController.selectObject(Hedwig.guideBrowserController.get("previousArticle"));
+ }
+});
8 apps/hedwig/responders/SOURCE.js
@@ -0,0 +1,8 @@
+/*global Hedwig */
+require("DEMO.js");
+Hedwig.SOURCE = SC.Responder.create({
+ nextResponder: Hedwig.DEMO,
+ didBecomeFirstResponder: function() {
+
+ }
+});
8 apps/hedwig/responders/START.js
@@ -0,0 +1,8 @@
+/*global Hedwig*/
+Hedwig.START = SC.Responder.create({
+ didBecomeFirstResponder: function() {
+ Hedwig.getPath('mainPage.mainPane').append() ;
+ Hedwig.guidesController.loadGuide(Hedwig.guidePath);
+ Hedwig.invokeLater("makeFirstResponder", 1, Hedwig.NORMAL);
+ }
+});
15 apps/hedwig/tests/controllers/article.js
@@ -0,0 +1,15 @@
+// ==========================================================================
+// Project: Hedwig.articleController Unit Test
+// Copyright: ©2010 My Company, Inc.
+// ==========================================================================
+/*globals Hedwig module test ok equals same stop start */
+
+module("Hedwig.articleController");
+
+// TODO: Replace with real unit test for Hedwig.articleController
+test("test description", function() {
+ var expected = "test";
+ var result = "test";
+ equals(result, expected, "test should equal test");
+});
+
15 apps/hedwig/tests/controllers/demo.js
@@ -0,0 +1,15 @@
+// ==========================================================================
+// Project: Hedwig.demoController Unit Test
+// Copyright: ©2010 My Company, Inc.
+// ==========================================================================
+/*globals Hedwig module test ok equals same stop start */
+
+module("Hedwig.demoController");
+
+// TODO: Replace with real unit test for Hedwig.demoController
+test("test description", function() {
+ var expected = "test";
+ var result = "test";
+ equals(result, expected, "test should equal test");
+});
+
15 apps/hedwig/tests/controllers/guide.js
@@ -0,0 +1,15 @@
+// ==========================================================================
+// Project: Hedwig.guideController Unit Test
+// Copyright: ©2010 My Company, Inc.
+// ==========================================================================
+/*globals Hedwig module test ok equals same stop start */
+
+module("Hedwig.guideController");
+
+// TODO: Replace with real unit test for Hedwig.guideController
+test("test description", function() {
+ var expected = "test";
+ var result = "test";
+ equals(result, expected, "test should equal test");
+});
+
15 apps/hedwig/tests/controllers/guide_browser.js
@@ -0,0 +1,15 @@
+// ==========================================================================
+// Project: Hedwig.guideBrowserController Unit Test
+// Copyright: ©2010 My Company, Inc.
+// ==========================================================================
+/*globals Hedwig module test ok equals same stop start */
+
+module("Hedwig.guideBrowserController");
+
+// TODO: Replace with real unit test for Hedwig.guideBrowserController
+test("test description", function() {
+ var expected = "test";
+ var result = "test";
+ equals(result, expected, "test should equal test");
+});
+
15 apps/hedwig/tests/controllers/guides.js
@@ -0,0 +1,15 @@
+// ==========================================================================
+// Project: Hedwig.guidesController Unit Test
+// Copyright: ©2010 My Company, Inc.
+// ==========================================================================
+/*globals Hedwig module test ok equals same stop start */
+
+module("Hedwig.guidesController");
+
+// TODO: Replace with real unit test for Hedwig.guidesController
+test("test description", function() {
+ var expected = "test";
+ var result = "test";
+ equals(result, expected, "test should equal test");
+});
+
15 apps/hedwig/tests/views/demo_panel.js
@@ -0,0 +1,15 @@
+// ==========================================================================
+// Project: Hedwig.DemoPanel Unit Test
+// Copyright: ©2010 My Company, Inc.
+// ==========================================================================
+/*globals Hedwig module test ok equals same stop start */
+
+module("Hedwig.DemoPanel");
+
+// TODO: Replace with real unit test for Hedwig.DemoPanel
+test("test description", function() {
+ var expected = "test";
+ var result = "test";
+ equals(result, expected, "test should equal test");
+});
+
41 apps/hedwig/views/demo_panel.js
@@ -0,0 +1,41 @@
+// ==========================================================================
+// Project: Hedwig.DemoPanel
+// Copyright: ©2010 My Company, Inc.
+// ==========================================================================
+/*globals Hedwig */
+
+/** @class
+
+ (Document Your View Here)
+
+ @extends SC.View
+*/
+Hedwig.DemoPanel = SC.PanelPane.extend(
+/** @scope Hedwig.DemoPanel.prototype */ {
+ defaultResponder: Hedwig,
+ layout: { top: 0, bottom: 0, width: 768, centerX: 0 },
+ contentView: null,
+ theme: "popover"
+});
+
+Hedwig.DemoPanel.generateWithView = function(view) {
+ return Hedwig.DemoPanel.create({
+ contentView: SC.WorkspaceView.design({
+ topToolbar: SC.ToolbarView.design({
+ childViews: "close source".w(), // not "closed" source-- close & source
+ close: SC.ButtonView.design({
+ layout: { left: 7, centerY: 0, height: 30, width: 100 },
+ title: "Close",
+ action: "closeDemo"
+ }),
+
+ source: SC.ButtonView.design({
+ layout: { right: 7, centerY: 0, height: 30, width: 100 },
+ title: "Source",
+ action: "showDemoSource"
+ })
+ }),
+ contentView: view
+ })
+ });
+};
102 docs/.json
@@ -0,0 +1,102 @@
+<html><head><title>Docs
+</title><meta http-equiv: 'Content-Type' content="text/html; charset=utf-8" />
+<style type="text/css">
+ body {
+ font-family: "Lucida Sans", "Lucida Grande", Verdana, Arial, sans-serif;
+ margin: 0px;
+ margin-bottom:1em;
+ font-family: sans-serif;
+ font-size: 10px;
+ line-height:1.2;
+ }
+
+ .content { font-size: 14px; }
+
+ code {
+ font-family: Monaco, Inconsolata, Courier, fixed-width;
+ font-size: 12px;
+ }
+
+ pre code {
+ margin-right: 1em;
+ border: 1px solid #a0b0a0;
+ overflow-y: hidden;
+ overflow-x: auto;
+ background: #f5f9f5;
+ display:block;
+ padding: 1em;
+ }
+
+ a { text-decoration: none; color: rgb(50, 50, 155); }
+
+
+ .header {
+ background-color: rgb(38, 43, 50);
+ height: 60px;
+ padding-top:17px;
+ padding-bottom:17px;
+ padding-left:2em;
+ }
+
+ .header a.img {
+ float: left;
+ }
+
+ .header .here {
+ float:left;
+ margin-top:27px;
+ margin-left:5px;
+ color: rgb(200, 255, 200);
+ font-size: 25px;
+ }
+
+ .header span.here {
+ margin-top:34px;
+ font-size:15px;
+ color: white;
+ }
+
+ .header a.item {
+ float:right;
+ margin-top: 34px;
+ margin-right:10px;
+ color: white;
+ font-size:15px;
+ }
+
+ .header a.item:hover {
+ text-decoration: underline;
+ }
+
+ .content {
+ padding-top: 1em;
+ padding-left: 2em;
+ padding-right: 2em;
+ }
+
+ img { margin-left: auto; margin-right: auto; display: block; }
+
+
+ h1, h2, h3 { color: rgb(100, 155, 100); }
+
+ code .class { color: rgb(0, 0, 150); }
+ /*code .variable { color: rgb(10, 70, 10); }*/
+ code .comment { color: rgb(100, 150, 200); }
+ code .string { color: rgb(0, 100, 10); }
+ code .number { color: rgb(0, 0, 255); }
+ code .keyword, code .this { color: rgb(25, 110, 25); font-weight: bold; }
+</style>
+</head><body><div class="header"><a href="../../index.html" class="img"><img src="../../resources/logo.png" /></a><a href="../../index.html" class="here">Documentation
+</a><a href="../../reference/index.html" class="item">SproutCore Reference
+</a></div><div class="content"><p>{
+ "any": "metadata",
+ "goes": "Here",
+ "damn": "gruber",
+ "this": "is still eye-readable",
+ "and": "He is wrong about touch apps."
+}</p>
+
+<h1>A Brief Touch on Touches</h1>
+
+<p>Some text goes here.</p>
+</div><div class="footer"></div></body></html>
98 docs/build/articles/touch/a-brief-touch.html
@@ -0,0 +1,98 @@
+<html><head><title>Docs
+</title><meta http-equiv: 'Content-Type' content="text/html; charset=utf-8" />
+<style type="text/css">
+ body {
+ font-family: "Lucida Sans", "Lucida Grande", Verdana, Arial, sans-serif;
+ margin: 0px;
+ margin-bottom:1em;
+ font-family: sans-serif;
+ font-size: 10px;
+ line-height:1.2;
+ }
+
+ .content { font-size: 14px; }
+
+ code {
+ font-family: Monaco, Inconsolata, Courier, fixed-width;
+ font-size: 12px;
+ }
+
+ pre code {
+ margin-right: 1em;
+ border: 1px solid #a0b0a0;
+ overflow-y: hidden;
+ overflow-x: auto;
+ background: #f5f9f5;
+ display:block;
+ padding: 1em;
+ }
+
+ a { text-decoration: none; color: rgb(50, 50, 155); }
+
+
+ .header {
+ background-color: rgb(38, 43, 50);
+ height: 60px;
+ padding-top:17px;
+ padding-bottom:17px;
+ padding-left:2em;
+ }
+
+ .header a.img {
+ float: left;
+ }
+
+ .header .here {
+ float:left;
+ margin-top:27px;
+ margin-left:5px;
+ color: rgb(200, 255, 200);
+ font-size: 25px;
+ }
+
+ .header span.here {
+ margin-top:34px;
+ font-size:15px;
+ color: white;
+ }
+
+ .header a.item {
+ float:right;
+ margin-top: 34px;
+ margin-right:10px;
+ color: white;
+ font-size:15px;
+ }
+
+ .header a.item:hover {
+ text-decoration: underline;
+ }
+
+ .content {
+ padding-top: 1em;
+ padding-left: 2em;
+ padding-right: 2em;
+ }
+
+ img { margin-left: auto; margin-right: auto; display: block; }
+
+
+ h1, h2, h3 { color: rgb(100, 155, 100); }
+
+ code .class { color: rgb(0, 0, 150); }
+ /*code .variable { color: rgb(10, 70, 10); }*/
+ code .comment { color: rgb(100, 150, 200); }
+ code .string { color: rgb(0, 100, 10); }
+ code .number { color: rgb(0, 0, 255); }
+ code .keyword, code .this { color: rgb(25, 110, 25); font-weight: bold; }
+</style>
+</head><body><div class="header"><a href="../../index.html" class="img"><img src="../../resources/logo.png" /></a><a href="../../index.html" class="here">Documentation
+</a><a href="../../reference/index.html" class="item">SproutCore Reference
+</a></div><div class="content"><h1>A Brief Touch on Touches</h1>
+
+<p>Some text goes here.</p>
+
+<p>Note that links like the below must appear on empty lines with no preceding whitespace.</p>
+
+<p><a href='touch-demo.js' class='demo'>touch-demo.js</a></p>
+</div><div class="footer"></div></body></html>
1  docs/build/articles/touch/a-brief-touch.json
@@ -0,0 +1 @@
+{"content":"<h1>A Brief Touch on Touches</h1>\n\n<p>Some text goes here.</p>\n\n<p>Note that links like the below must appear on empty lines with no preceding whitespace.</p>\n\n<p><a href='touch-demo.js' class='demo'>touch-demo.js</a></p>","errors":[],"demos":{"touch-demo.js":"var MyExampleView = SC.View.extend({\n backgroundColor: \"green\"\n});\n\n// bootstrap code :)\nexports.getDemoView = function() {\n return MyExampleView;\n};"},"articleDirectory":"articles/touch/","outputDirectory":"build/","title":"A Brief Touch on Touches","any":"metadata","goes":"Here","damn":"gruber","this":"is still eye-readable","and":"He is wrong about touch apps."}
15 docs/build/articles/touch/a-brief-touch.md
@@ -0,0 +1,15 @@
+{
+ "any": "metadata",
+ "goes": "Here",
+ "damn": "gruber",
+ "this": "is still eye-readable",
+ "and": "He is wrong about touch apps."
+}
+
+A Brief Touch on Touches
+========================
+Some text goes here.
+
+Note that links like the below must appear on empty lines with no preceding whitespace.
+
+{{demo:sc|touch-demo.js}}
8 docs/build/articles/touch/touch-demo.js
@@ -0,0 +1,8 @@
+var MyExampleView = SC.View.extend({
+ backgroundColor: "red"
+});
+
+// bootstrap code :)
+exports.getDemoView = function() {
+ return MyExampleView;
+};
92 docs/build/articles/touch/touch-events.html
@@ -0,0 +1,92 @@
+<html><head><title>Docs
+</title><meta http-equiv: 'Content-Type' content="text/html; charset=utf-8" />
+<style type="text/css">
+ body {
+ font-family: "Lucida Sans", "Lucida Grande", Verdana, Arial, sans-serif;
+ margin: 0px;
+ margin-bottom:1em;
+ font-family: sans-serif;
+ font-size: 10px;
+ line-height:1.2;
+ }
+
+ .content { font-size: 14px; }
+
+ code {
+ font-family: Monaco, Inconsolata, Courier, fixed-width;
+ font-size: 12px;
+ }
+
+ pre code {
+ margin-right: 1em;
+ border: 1px solid #a0b0a0;
+ overflow-y: hidden;
+ overflow-x: auto;
+ background: #f5f9f5;
+ display:block;
+ padding: 1em;
+ }
+
+ a { text-decoration: none; color: rgb(50, 50, 155); }
+
+
+ .header {
+ background-color: rgb(38, 43, 50);
+ height: 60px;
+ padding-top:17px;
+ padding-bottom:17px;
+ padding-left:2em;
+ }
+
+ .header a.img {
+ float: left;
+ }
+
+ .header .here {
+ float:left;
+ margin-top:27px;
+ margin-left:5px;
+ color: rgb(200, 255, 200);
+ font-size: 25px;
+ }
+
+ .header span.here {
+ margin-top:34px;
+ font-size:15px;
+ color: white;
+ }
+
+ .header a.item {
+ float:right;
+ margin-top: 34px;
+ margin-right:10px;
+ color: white;
+ font-size:15px;
+ }
+
+ .header a.item:hover {
+ text-decoration: underline;
+ }
+
+ .content {
+ padding-top: 1em;
+ padding-left: 2em;
+ padding-right: 2em;
+ }
+
+ img { margin-left: auto; margin-right: auto; display: block; }
+
+
+ h1, h2, h3 { color: rgb(100, 155, 100); }
+
+ code .class { color: rgb(0, 0, 150); }
+ /*code .variable { color: rgb(10, 70, 10); }*/
+ code .comment { color: rgb(100, 150, 200); }
+ code .string { color: rgb(0, 100, 10); }
+ code .number { color: rgb(0, 0, 255); }
+ code .keyword, code .this { color: rgb(25, 110, 25); font-weight: bold; }
+</style>
+</head><body><div class="header"><a href="../../index.html" class="img"><img src="../../resources/logo.png" /></a><a href="../../index.html" class="here">Documentation
+</a><a href="../../reference/index.html" class="item">SproutCore Reference
+</a></div><div class="content">
+</div><div class="footer"></div></body></html>
1  docs/build/articles/touch/touch-events.json
@@ -0,0 +1 @@
+{"content":"","errors":[],"demos":{},"articleDirectory":"articles/touch/","outputDirectory":"build/"}
0  docs/build/articles/touch/touch-events.md
No changes.
26 docs/build/guides/touch.guide
@@ -0,0 +1,26 @@
+{
+ "title": "Writing Touch Applications",
+ "sections": [
+ {
+ "title": "Introduction",
+ "articles": [
+ "articles/touch/a-brief-touch",
+ "articles/touch/touch-events"
+ ]
+ },
+ {
+ "title": "Built-In Support",
+ "articles": [
+ "articles/controls/button/touch",
+ "articles/controls/scroll/touch"
+ ]
+ },
+ {
+ "title": "Advanced Concepts",
+ "articles": [
+ "articles/touch/capturing",
+ "articles/touch/releasing"
+ ]
+ }
+ ]
+}
1  docs/build/guides/touch.json
@@ -0,0 +1 @@
+{"title":"Writing Touch Applications","sections":[{"title":"Introduction","articles":[{"content":"<h1>A Brief Touch on Touches</h1>\n\n<p>Some text goes here.</p>\n\n<p>Note that links like the below must appear on empty lines with no preceding whitespace.</p>\n\n<p><a href='touch-demo.js' class='demo'>touch-demo.js</a></p>","errors":[],"demos":{"touch-demo.js":"var MyExampleView = SC.View.extend({\n backgroundColor: \"red\"\n});\n\n// bootstrap code :)\nexports.getDemoView = function() {\n return MyExampleView;\n};"},"articleDirectory":"articles/touch/","outputDirectory":"build/","title":"A Brief Touch on Touches","any":"metadata","goes":"Here","damn":"gruber","this":"is still eye-readable","and":"He is wrong about touch apps."},{"content":"","errors":[],"demos":{},"articleDirectory":"articles/touch/","outputDirectory":"build/"}]},{"title":"Built-In Support","articles":[{"error":{"message":"No such file or directory","stack":"Error: No such file or directory\n at Object.readFileSync (fs:74:20)\n at Object.readFile (/Users/alex/.seeds/packages/core-support/0.2.1/lib/fs.js:348:37)\n at /Volumes/Docs/Work/web/hedwig/docs/hoo/lib/guide.js:9:27\n at /Volumes/Docs/Work/web/hedwig/docs/hoo/lib/guide.js:22:48\n at Object.process (/Volumes/Docs/Work/web/hedwig/docs/hoo/lib/guide.js:30:38)\n at /Volumes/Docs/Work/web/hedwig/docs/hoo/bin/hoo-worker:55:17\n at Stream.<anonymous> (/Volumes/Docs/Work/web/hedwig/docs/hoo/packages/spawn/lib/spawn.js:63:25)\n at IOWatcher.callback (net:310:14)\n at node.js:818:9","errno":2},"file":"build/articles/controls/button/touch.json"},{"error":{"message":"No such file or directory","stack":"Error: No such file or directory\n at Object.readFileSync (fs:74:20)\n at Object.readFile (/Users/alex/.seeds/packages/core-support/0.2.1/lib/fs.js:348:37)\n at /Volumes/Docs/Work/web/hedwig/docs/hoo/lib/guide.js:9:27\n at /Volumes/Docs/Work/web/hedwig/docs/hoo/lib/guide.js:22:48\n at Object.process (/Volumes/Docs/Work/web/hedwig/docs/hoo/lib/guide.js:30:38)\n at /Volumes/Docs/Work/web/hedwig/docs/hoo/bin/hoo-worker:55:17\n at Stream.<anonymous> (/Volumes/Docs/Work/web/hedwig/docs/hoo/packages/spawn/lib/spawn.js:63:25)\n at IOWatcher.callback (net:310:14)\n at node.js:818:9","errno":2},"file":"build/articles/controls/scroll/touch.json"}]},{"title":"Advanced Concepts","articles":[{"error":{"message":"No such file or directory","stack":"Error: No such file or directory\n at Object.readFileSync (fs:74:20)\n at Object.readFile (/Users/alex/.seeds/packages/core-support/0.2.1/lib/fs.js:348:37)\n at /Volumes/Docs/Work/web/hedwig/docs/hoo/lib/guide.js:9:27\n at /Volumes/Docs/Work/web/hedwig/docs/hoo/lib/guide.js:22:48\n at Object.process (/Volumes/Docs/Work/web/hedwig/docs/hoo/lib/guide.js:30:38)\n at /Volumes/Docs/Work/web/hedwig/docs/hoo/bin/hoo-worker:55:17\n at Stream.<anonymous> (/Volumes/Docs/Work/web/hedwig/docs/hoo/packages/spawn/lib/spawn.js:63:25)\n at IOWatcher.callback (net:310:14)\n at node.js:818:9","errno":2},"file":"build/articles/touch/capturing.json"},{"error":{"message":"No such file or directory","stack":"Error: No such file or directory\n at Object.readFileSync (fs:74:20)\n at Object.readFile (/Users/alex/.seeds/packages/core-support/0.2.1/lib/fs.js:348:37)\n at /Volumes/Docs/Work/web/hedwig/docs/hoo/lib/guide.js:9:27\n at /Volumes/Docs/Work/web/hedwig/docs/hoo/lib/guide.js:22:48\n at Object.process (/Volumes/Docs/Work/web/hedwig/docs/hoo/lib/guide.js:30:38)\n at /Volumes/Docs/Work/web/hedwig/docs/hoo/bin/hoo-worker:55:17\n at Stream.<anonymous> (/Volumes/Docs/Work/web/hedwig/docs/hoo/packages/spawn/lib/spawn.js:63:25)\n at IOWatcher.callback (net:310:14)\n at node.js:818:9","errno":2},"file":"build/articles/touch/releasing.json"}]}],"file":"build/guides/touch"}
98 docs/build/template.haml
@@ -0,0 +1,98 @@
+%html
+ %head
+ %title Docs
+ %meta{http-equiv: 'Content-Type' content: 'text/html; charset=utf-8'}
+ :css
+ body {
+ font-family: "Lucida Sans", "Lucida Grande", Verdana, Arial, sans-serif;
+ margin: 0px;
+ margin-bottom:1em;
+ font-family: sans-serif;
+ font-size: 10px;
+ line-height:1.2;
+ }
+
+ .content { font-size: 14px; }
+
+ code {
+ font-family: Monaco, Inconsolata, Courier, fixed-width;
+ font-size: 12px;
+ }
+
+ pre code {
+ margin-right: 1em;
+ border: 1px solid #a0b0a0;
+ overflow-y: hidden;
+ overflow-x: auto;
+ background: #f5f9f5;
+ display:block;
+ padding: 1em;
+ }
+
+ a { text-decoration: none; color: rgb(50, 50, 155); }
+
+
+ .header {
+ background-color: rgb(38, 43, 50);
+ height: 60px;
+ padding-top:17px;
+ padding-bottom:17px;
+ padding-left:2em;
+ }
+
+ .header a.img {
+ float: left;
+ }
+
+ .header .here {
+ float:left;
+ margin-top:27px;
+ margin-left:5px;
+ color: rgb(200, 255, 200);
+ font-size: 25px;
+ }
+
+ .header span.here {
+ margin-top:34px;
+ font-size:15px;
+ color: white;
+ }
+
+ .header a.item {
+ float:right;
+ margin-top: 34px;
+ margin-right:10px;
+ color: white;
+ font-size:15px;
+ }
+
+ .header a.item:hover {
+ text-decoration: underline;
+ }
+
+ .content {
+ padding-top: 1em;
+ padding-left: 2em;
+ padding-right: 2em;
+ }
+
+ img { margin-left: auto; margin-right: auto; display: block; }
+
+
+ h1, h2, h3 { color: rgb(100, 155, 100); }
+
+ code .class { color: rgb(0, 0, 150); }
+ /*code .variable { color: rgb(10, 70, 10); }*/
+ code .comment { color: rgb(100, 150, 200); }
+ code .string { color: rgb(0, 100, 10); }
+ code .number { color: rgb(0, 0, 255); }
+ code .keyword, code .this { color: rgb(25, 110, 25); font-weight: bold; }
+ %body
+ .header
+ %a.img{ href: static_url('index.html') }
+ %img{ src: static_url('resources/logo.png') }
+ %a.here{href:static_url('index.html')} Documentation
+ %a.item{href:static_url('reference/index.html')} SproutCore Reference
+ .content
+ = this.contents
+ .footer
1  docs/hoo
@@ -0,0 +1 @@
+Subproject commit 5fbab968a484270dc94b294205dac4064d263928
15 docs/src/articles/touch/a-brief-touch.md
@@ -0,0 +1,15 @@
+{
+ "any": "metadata",
+ "goes": "Here",
+ "damn": "gruber",
+ "this": "is still eye-readable",
+ "and": "He is wrong about touch apps."
+}
+
+A Brief Touch on Touches
+========================
+Some text goes here.
+
+Note that links like the below must appear on empty lines with no preceding whitespace.
+
+{{demo:sc|touch-demo.js}}
8 docs/src/articles/touch/touch-demo.js
@@ -0,0 +1,8 @@
+var MyExampleView = SC.View.extend({
+ backgroundColor: "red"
+});
+
+// bootstrap code :)
+exports.getDemoView = function() {
+ return MyExampleView;
+};
0  docs/src/articles/touch/touch-events.md
No changes.
26 docs/src/guides/touch.guide
@@ -0,0 +1,26 @@
+{
+ "title": "Writing Touch Applications",
+ "sections": [
+ {
+ "title": "Introduction",
+ "articles": [
+ "articles/touch/a-brief-touch",
+ "articles/touch/touch-events"
+ ]
+ },
+ {
+ "title": "Built-In Support",
+ "articles": [
+ "articles/controls/button/touch",
+ "articles/controls/scroll/touch"
+ ]
+ },
+ {
+ "title": "Advanced Concepts",
+ "articles": [
+ "articles/touch/capturing",
+ "articles/touch/releasing"
+ ]
+ }
+ ]
+}
98 docs/src/template.haml
@@ -0,0 +1,98 @@
+%html
+ %head
+ %title Docs
+ %meta{http-equiv: 'Content-Type' content: 'text/html; charset=utf-8'}
+ :css
+ body {
+ font-family: "Lucida Sans", "Lucida Grande", Verdana, Arial, sans-serif;
+ margin: 0px;
+ margin-bottom:1em;
+ font-family: sans-serif;
+ font-size: 10px;
+ line-height:1.2;
+ }
+
+ .content { font-size: 14px; }
+
+ code {
+ font-family: Monaco, Inconsolata, Courier, fixed-width;
+ font-size: 12px;
+ }
+
+ pre code {
+ margin-right: 1em;
+ border: 1px solid #a0b0a0;
+ overflow-y: hidden;
+ overflow-x: auto;
+ background: #f5f9f5;
+ display:block;
+ padding: 1em;
+ }
+
+ a { text-decoration: none; color: rgb(50, 50, 155); }
+
+
+ .header {
+ background-color: rgb(38, 43, 50);
+ height: 60px;
+ padding-top:17px;
+ padding-bottom:17px;
+ padding-left:2em;
+ }
+
+ .header a.img {
+ float: left;
+ }
+
+ .header .here {
+ float:left;
+ margin-top:27px;
+ margin-left:5px;
+ color: rgb(200, 255, 200);
+ font-size: 25px;
+ }
+
+ .header span.here {
+ margin-top:34px;
+ font-size:15px;
+ color: white;
+ }
+
+ .header a.item {
+ float:right;
+ margin-top: 34px;
+ margin-right:10px;
+ color: white;
+ font-size:15px;
+ }
+
+ .header a.item:hover {
+ text-decoration: underline;
+ }
+
+ .content {
+ padding-top: 1em;
+ padding-left: 2em;
+ padding-right: 2em;
+ }
+
+ img { margin-left: auto; margin-right: auto; display: block; }
+
+
+ h1, h2, h3 { color: rgb(100, 155, 100); }
+
+ code .class { color: rgb(0, 0, 150); }
+ /*code .variable { color: rgb(10, 70, 10); }*/
+ code .comment { color: rgb(100, 150, 200); }
+ code .string { color: rgb(0, 100, 10); }
+ code .number { color: rgb(0, 0, 255); }
+ code .keyword, code .this { color: rgb(25, 110, 25); font-weight: bold; }
+ %body
+ .header
+ %a.img{ href: static_url('index.html') }
+ %img{ src: static_url('resources/logo.png') }
+ %a.here{href:static_url('index.html')} Documentation
+ %a.item{href:static_url('reference/index.html')} SproutCore Reference
+ .content
+ = this.contents
+ .footer
98 docs/template.haml
@@ -0,0 +1,98 @@
+%html
+ %head
+ %title Docs
+ %meta{http-equiv: 'Content-Type' content: 'text/html; charset=utf-8'}
+ :css
+ body {
+ font-family: "Lucida Sans", "Lucida Grande", Verdana, Arial, sans-serif;
+ margin: 0px;
+ margin-bottom:1em;
+ font-family: sans-serif;
+ font-size: 10px;
+ line-height:1.2;
+ }
+
+ .content { font-size: 14px; }
+
+ code {
+ font-family: Monaco, Inconsolata, Courier, fixed-width;
+ font-size: 12px;
+ }
+
+ pre code {
+ margin-right: 1em;
+ border: 1px solid #a0b0a0;
+ overflow-y: hidden;
+ overflow-x: auto;
+ background: #f5f9f5;
+ display:block;
+ padding: 1em;
+ }
+
+ a { text-decoration: none; color: rgb(50, 50, 155); }
+
+
+ .header {
+ background-color: rgb(38, 43, 50);
+ height: 60px;
+ padding-top:17px;
+ padding-bottom:17px;
+ padding-left:2em;
+ }
+
+ .header a.img {
+ float: left;
+ }
+
+ .header .here {
+ float:left;
+ margin-top:27px;
+ margin-left:5px;
+ color: rgb(200, 255, 200);
+ font-size: 25px;
+ }
+
+ .header span.here {
+ margin-top:34px;
+ font-size:15px;
+ color: white;
+ }
+
+ .header a.item {
+ float:right;
+ margin-top: 34px;
+ margin-right:10px;
+ color: white;
+ font-size:15px;
+ }
+
+ .header a.item:hover {
+ text-decoration: underline;
+ }
+
+ .content {
+ padding-top: 1em;
+ padding-left: 2em;
+ padding-right: 2em;
+ }
+
+ img { margin-left: auto; margin-right: auto; display: block; }
+
+
+ h1, h2, h3 { color: rgb(100, 155, 100); }
+
+ code .class { color: rgb(0, 0, 150); }
+ /*code .variable { color: rgb(10, 70, 10); }*/
+ code .comment { color: rgb(100, 150, 200); }
+ code .string { color: rgb(0, 100, 10); }
+ code .number { color: rgb(0, 0, 255); }
+ code .keyword, code .this { color: rgb(25, 110, 25); font-weight: bold; }
+ %body
+ .header
+ %a.img{ href: static_url('index.html') }
+ %img{ src: static_url('resources/logo.png') }
+ %a.here{href:static_url('index.html')} Documentation
+ %a.item{href:static_url('reference/index.html')} SproutCore Reference
+ .content
+ = this.content
+ .footer
1  frameworks/sproutcore
@@ -0,0 +1 @@
+Subproject commit 7bb2bcd5636bea3fad6285c368ecc2c626041e80
1  themes/pig/Buildfile
@@ -0,0 +1 @@
+config :all, :required => "sproutcore/ace"
2  themes/pig/build
@@ -0,0 +1,2 @@
+#!/usr/bin/env ruby
+system('chance/chance.rb --less pig')
2  themes/pig/build-d
@@ -0,0 +1,2 @@
+#!/usr/bin/env ruby
+system('chance/chance.rb --data-url --less ace.light.pig')
113 themes/pig/chance/README.md
@@ -0,0 +1,113 @@
+Description
+===========
+Chance is a set of ruby scripts to auto-generate sprites from your CSS.
+It only requires two special tags in your css to work.
+
+Contributions
+=============
+* Alex Iskander (http://create.tpsitulsa.com/blog/) -- All credit for the initial idea
+* Joshua Holt (http://blog.thesempiternalholts.com) -- Modifications and Additions
+* Colin Campbell (-----------------------------------) -- Modifications and Additions
+
+Build Tools
+===========
+The operation works inside the theme folder, and generates the "resources" folder used
+by SproutCore. There are various options that can be seen by calling with the --help argument.
+
+The theme packaging operation is recursive (much like SproutCore's build tools), so
+any folder depth may be used. The suggested folder layout is this:
+
+* Theme Folder
+ * View Category Folders (controls, containers, etc.)
+ * Views (button\_view, progress\_view, etc.)
+ * 1 CSS file
+ * 0+ images (PSDs, usually)
+
+Each CSS file will reference images relative to itself. So, controls/progress\_view/progress_view.css
+could reference "progress\_view.png".
+
+CSS Syntax
+----------
+Normal CSS won't work too well for accessing Sprites. It will work even less when
+you need to perform slicing (do not talk to me about my nemesis, Photoshop slicing).
+
+However, I do not want to parse CSS, so I use regular expressions.
+
+Here is the current syntax:
+
+ @view(view-name) .more-rules {
+ /*
+ Input file, repeat, anchor, slice rect
+ */
+ background: sprite("progress_view_track.png" repeat-x [12 1]);
+ background: sprite("progress_view_track.png" anchor-right [-8])
+ background: sprite("progress_view_track.png" anchor-right [1 1 5 1]) /* 1,1; size: 5, 1 */
+ }
+
+The build tools would just search for sprite(, and then parse the contents, and replace @view(view-name)
+with .sc-view.view-name.theme.name (where theme.name is specified via an argument to the build tool).
+Note: right now, it does not do anything with the theme name; this may change in future.
+
+The syntax is:
+
+ sprite(<sprite name> [<repeat method>] [clear] [<anchor method>] [<rect or partial rect>])
+
+ Sprite name: the name of the image (quotes required only for images with spaces)
+
+ Repeat Method: repeat-x or repeat-y
+
+ clear: Whether to ensure there are no more images on the row after this one.
+ Use with anchor-left to ensure a lonely item.
+
+ Anchor Method: anchor-left or anchor-right (forces the image to be on left or right side of image;
+ see below)
+
+ Partial Rectangle: \[ left [width] \] // left can be positive or negative.
+ Rectangle: \[ left top width height \]
+
+It is rather trivial to parse, yet also easy to read.
+
+
+Chance also provides declarations for easily providing cross-browser compatible CSS rules, including
+border-radius and box-shadow. Here is example input:
+
+ .class {
+ -sc-box-shadow: 0 0 5px #000;
+ -sc-border-radius: 5px;
+ /*
+ Also supported:
+ -sc-border-top-left-radius
+ -sc-border-top-right-radius
+ -sc-border-bottom-left-radius
+ -sc-border-bottom-right-radius
+ */
+ }
+
+Chance will then output the following, allowing for a single declaration to cover all browsers:
+
+ .class {
+ -moz-box-shadow: 0 0 5px #000; -webkit-box-shadow: 0 0 5px #000; box-shadow: 0 0 5px #000;
+ -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px;
+ }
+
+
+Anchoring
+---------
+Anchoring an image to the left or right side allows you to effectively create controls that have left,
+right, and middle parts. Such controls are usually easy to make, but not if the control can shrink to
+0px (like ProgressView).
+
+For ProgressView, the control is created like this:
+
+container w/left portion
+ inner-head with right portion: anchor-right, left: 8, right: 0
+ The left:8 right:0 allows it to never overlap the left edge.
+ inner-tail with middle portion: left: 8, right: 8
+ Writes over any junk that comes before the right-anchored part.
+
+
+Producing Production-Ready Files
+================================
+It would be best to open the files that are in resources/images in Photoshop and Save for Web. This will
+reduce the size by a large margin. For instance, in one test, the main png started at ~24,000 bytes, and
+after going through PS, finished at ~17,000.
173 themes/pig/chance/chance.rb
@@ -0,0 +1,173 @@
+#!/usr/bin/env ruby
+
+# A CSSParser object is created for every file processed. It opens the file,
+# reads its contents, and can perform two actions on it: parsing and generating.
+#
+# The parsing step reads the file and finds references to images; this causes those
+# images to be added to the object's usedImages, which is a set of images required (images
+# here being hashes with arguments specified). Ruby's "Set" functionality seems
+# to work here (I am not a ruby expert, hence 'seems')
+#
+# Internally, it actually generates an in-memory CSS file in the parsing step,
+# to be used in the generating step—but that's all transparent. The object is long-lived,
+# staying around from initial parse step to generation.
+#
+# In my opinion, this script is not that great. This is due to two reasons: it is my very
+# first script written in Ruby, and I was figuring out the requirements by writing it (something
+# I often do, but I usually follow that with a rewrite).
+# But still, it generally works, and works with acceptable speed.
+
+$: << File.expand_path(File.dirname(__FILE__) + "/lib")
+
+require 'css'
+require 'slicedice'
+
+require 'optparse'
+require 'pp'
+require 'FileUtils'
+
+config = {}
+argparser = OptionParser.new {|opts|
+ opts.banner = "Usage: ruby generate_theme.rb [options] theme.name"
+
+ config[:optimization_limit] = 50000
+ opts.on(
+ '-l',
+ '--optimization-limit [limit]', Integer,
+ "Optimization limit; if reducing latency by eliminating a file causes more " +
+ "than [limit] wasted pixels, it won't be done."
+ ) {|limit|
+ config[:optimization_limit] = limit
+ }
+
+ opts.on('--prefer-fewer-files', "Prefer fewer files by setting the optimization limit to 2 million pixels.") {
+ config[:optimization_limit] = 2000000
+ }
+
+ opts.on('--prefer-smaller-download', "Prefer smaller download by setting the optimization limit to 0 pixels.") {
+ config[:optimization_limit] = 0
+ }
+
+
+ config[:input] = "./src/"
+ opts.on('-i', '--input [directory]', "Input directory (default: ./)") {|input|
+ config[:input] = input
+ }
+
+ config[:output] = "resources/"
+ opts.on('-o', '--output [directory]', 'Set output path (default: resources/)') {|out|
+ out += "." if out.length == 0
+ out += "/" if out[out.length - 1] != '/'
+ config[:output] = out
+ }
+
+ config[:url_template] = "static_url(\"%s\")"
+ opts.on('-u', '--url [PATTERN]', 'The URL template (default: static_url(\"%s\") )') {|template|
+ config[:url_template] = template
+ }
+
+ config[:extension] = "css"
+ opts.on('-e', '--extension [ext]', "The extension for CSS files.") {|ext|
+ config[:extension] = ext
+ }
+
+ config[:less] = false
+ opts.on('--less', "Postprocess using LESS.") {
+ config[:less] = true
+ }
+
+ config[:include_sc_theme] = true
+ opts.on('--no-sc-theme', "Exclude 'sc-theme' prefix (newer themes with SC 1.1 can do this).") {
+ config[:include_sc_theme] = false
+ }
+
+ config[:use_data_url] = false
+ opts.on('--data-url', "Use data urls embedded in the CSS.") {
+ config[:use_data_url] = true
+ }
+}
+
+argparser.parse!
+if ARGV.length > 0
+ config[:theme_name] = ARGV[0]
+end
+
+require 'find'
+images = {}
+static_images = {}
+parsers = []
+Find.find(config[:input]) do |f|
+ if f =~ /^\.\/(resources)|\/\./
+ Find.prune
+ end
+ if f =~ /\.css$/
+ parser = CSSParser.new(File.dirname(f), File.basename(f), config)
+ parsers << parser
+ parser.parse
+ images.merge! parser.images
+ static_images.merge! parser.static_images
+ end
+end
+
+#---------------------------------------------------------------------------
+# TODO: ?
+# I should probably make this a boolean option and wrap this in an if
+# statement based on that option.
+#---------------------------------------------------------------------------
+# Copy all non-sprited images to respective dirs.
+if static_images.length > 0
+ static_images.values.each do |hsh|
+ src_path = hsh[:path]
+ match = /((\/images\/.*)(\/.*(\.png|\.jpg|\.gif)))/.match(src_path)
+ if match
+ location = match[2]
+ new_loc = "#{config[:output].sub(/\/$/,'')}#{location}"
+ FileUtils.mkdir_p(new_loc) if !File.exist?(new_loc)
+ FileUtils.cp("#{src_path}", new_loc) if File.exist?(src_path)
+ end
+ end
+end
+# END Non-sprited image copy
+
+
+slicer = Slicer.new(config)
+slicer.images = images
+slicer.slice
+slicer.dice
+
+# Add all the code together
+css_code = ""
+parsers.each {|parser|
+ parser.images = slicer.images
+ css_code += parser.generate + "\n"
+}
+
+# Do some cleanup of whitespace
+cleaned = ""
+css_code.each_line {|line|
+ cleaned += line.strip + "\n"
+}
+
+final = cleaned
+
+if config[:less]
+ begin
+ require 'less'
+ rescue
+ raise "Lest less doth be installed, less shall remain unusable. Sad."
+ end
+
+ lessed = Less.parse cleaned
+ final = ""
+
+ # LESS messes things up
+ lessed.each_line {|line|
+ final += line.gsub(/url\('data:image\/png;base64,(.*?)'\)/) do |match|
+ res = $1.gsub(" ", "+")
+ "url('data:image/png;base64," + res + "')"
+ end
+ }
+end
+
+
+File.open(config[:output] + "theme." + config[:extension], File::WRONLY|File::TRUNC|File::CREAT) {|f| f.write(final) }
202 themes/pig/chance/lib/css.rb
@@ -0,0 +1,202 @@
+require 'set'
+require 'base64'
+class CSSParser
+ attr_accessor :images, :contents, :static_images
+ def initialize(directory, file, config)
+ @directory = directory
+ @file = file
+ @theme = ""
+ if !config[:theme_name].nil?
+ @theme = "." + config[:theme_name]
+ end
+ @config = config
+ @images = { }
+ @static_images = { }
+ end
+
+ def parse
+ # first, read file
+ file = File.new(@directory + "/" + @file)
+ contents = ""
+ file.each {|line| contents += line}
+ @contents = contents
+
+ self.parse_rules
+ self.parse_sprites
+ end
+
+ def parse_rules
+ # parses @theme "name"
+ # and @view(viewname)
+ contents = @contents
+
+ view_rule = /(@view|@theme|@end)(\(\s*(["']{2}|["'].*?[^\\]"|[^\s]+)\s*\))?;?/
+
+ theme_name = @theme
+ theme_parts = [theme_name]
+ contents.gsub!(view_rule) do |match|
+ #".sc-view." + $1 + "." + theme_name # If SproutCore changes some
+ if $1 == "@view"
+ tn = ""
+ if @config[:include_sc_theme]
+ tn = ".sc-theme "
+ end
+ tn + ".sc-view" + theme_name + "." + $3
+ elsif $1 == "@theme"
+ theme_parts.push $3
+ theme_name = theme_parts.join "."
+ ""
+ elsif $1 == "@end"
+ theme_parts.pop
+ theme_name = theme_parts.join "."
+ ""
+ end
+ end
+
+ boxshadow_rule = /\-sc\-box\-shadow:\s*([^;]*)/
+ contents.gsub!(boxshadow_rule) do |match|
+ "-moz-box-shadow: " + $1 + "; -webkit-box-shadow: " + $1 + "; box-shadow: " + $1
+ end
+
+ borderradius_rule = /\-sc\-border\-radius:\s*([^;]*)/
+ contents.gsub!(borderradius_rule) do |match|
+ "-moz-border-radius: " + $1 + "; -webkit-border-radius: " + $1 + "; border-radius: " + $1
+ end
+
+ borderradius_topleft_rule = /\-sc\-border\-top\-left\-radius:\s*([^;]*)/
+ contents.gsub!(borderradius_topleft_rule) do |match|
+ "-moz-border-radius-topleft: " + $1 + "; -webkit-border-top-left-radius: " + $1 + "; border-top-left-radius: " + $1
+ end
+
+ borderradius_topright_rule = /\-sc\-border\-top\-right\-radius:\s*([^;]*)/
+ contents.gsub!(borderradius_topright_rule) do |match|
+ "-moz-border-radius-topright: " + $1 + "; -webkit-border-top-right-radius: " + $1 + "; border-top-right-radius: " + $1
+ end
+
+ borderradius_bottomleft_rule = /\-sc\-border\-bottom\-left\-radius:\s*([^;]*)/
+ contents.gsub!(borderradius_bottomleft_rule) do |match|
+ "-moz-border-radius-bottomleft: " + $1 + "; -webkit-border-bottom-left-radius: " + $1 + "; border-bottom-left-radius: " + $1
+ end
+
+ borderradius_bottomright_rule = /\-sc\-border\-bottom\-right\-radius:\s*([^;]*)/
+ contents.gsub!(borderradius_bottomright_rule) do |match|
+ "-moz-border-radius-bottomright: " + $1 + "; -webkit-border-bottom-right-radius: " + $1 + "; border-bottom-right-radius: " + $1
+ end
+
+ @contents = contents
+ end
+
+ def parse_sprites
+ contents = @contents
+
+ # A whole regexp would include: ([\s,]+(repeat-x|repeat-y))?([\s,]+\[(.*)\])?
+ # but let's keep it simple:
+ sprite_directive = /(sprite|static_url)\(\s*(["']{2}|["'].*?[^\\]['"]|[^\s]+)(.*?)\s*\)/
+ contents = contents.gsub(sprite_directive) do | match |
+ # prepare replacement string
+ replace_with_prefix = "sprite_for("
+ replace_with_suffix = ")"
+
+ # get name and add to replacement
+ type = $1
+ image_name = $2
+ args = $3
+ image_name = $2.sub(/^["'](.*)["']$/, '\1')
+
+ result_hash = {
+ :path => File.expand_path(@directory + "/" + image_name), :image => image_name,
+ :repeat => "no-repeat", :rect => [], :target => "",
+ :anchor => :none, :clear => false, :nosprite => (type == "static_url")
+ }
+
+ # Replacement string is made to be replaced again in a second pass
+ # first pass generates manifest, second pass actually puts sprite info in.
+
+ # match: key words (Separated by whitespace) or rects.
+ args.scan(/(\[.*?\]|[^\s]+)/) {|r|
+ arg = $1.strip
+
+ if arg.match(/^\[/)
+ # A rectangle specifying a slice
+ full_rect = []
+ params = arg.gsub(/^\[|\]$/, "").split(/[,\s]+/)
+ if params.length == 1
+ full_rect = [params[0].to_i, 0, 0, 0]
+ elsif params.length == 2
+ full_rect = [params[0].to_i, 0, params[1].to_i, 0]
+ elsif params.length == 4
+ full_rect = params.map {|e| e.to_i}
+ else
+
+ end
+
+ result_hash[:rect] = full_rect
+ else
+ # a normal keyword, probably.
+ if arg == "repeat-x"
+ result_hash[:repeat] = "repeat-x"
+ elsif arg == "repeat-y"
+ result_hash[:repeat] = "repeat-y"
+ elsif arg == "anchor-right"
+ result_hash[:anchor] = :right
+ elsif arg == "anchor-left"
+ result_hash[:anchor] = :left
+ elsif arg == "clear"
+ result_hash[:clear] = true
+ end
+ end
+ }
+
+ image_key = result_hash[:repeat] + ":" + result_hash[:rect].join(",") + ":" + result_hash[:path]
+ replace_with = replace_with_prefix + image_key + replace_with_suffix
+ @images[image_key] = result_hash
+
+ replace_with
+ end
+
+ @contents = contents
+ end
+
+ def generate
+ contents = @contents
+ contents = contents.gsub(/sprite_for\(\s*(["']{2}|["'].*?[^\\]["']|.*?)\s*\)/) {|match|
+ key = $1
+ result = ""
+ if @images.key? key
+ image = @images[key]
+
+ # if it is not sprited or we are not doing data urls, we use the url template.
+ if image[:nosprite] or not @config[:use_data_url]
+ result = (@config[:url_template] % [image[:sprite_path]])
+ else
+ # otherwise, we need to use a data url.
+ data = image[:image].to_blob
+ data = Base64.encode64(data)
+ data = data.gsub(/[ \n]/, '')
+
+ result = "url(\'data:image/png;base64,#{data}\')"
+ end
+
+ # Only put repeat data if not sprited
+ if not image[:nosprite]
+ result += " #{image[:repeat]}"
+
+ if not @config[:use_data_url]
+ if image[:anchor] == :none
+ result += image[:sprite_x] == 0 ? " #{image[:sprite_x]}" : " -#{image[:sprite_x]}px"
+ else
+ result += (image[:anchor] == :right ? " right" : " left")
+ end
+ result += image[:sprite_y] == 0 ? " #{image[:sprite_y]}" : " -#{image[:sprite_y]}px"
+ end
+ end
+
+ else
+ puts "Did not find image with key: ", key
+ end
+
+ result
+ }
+ return contents
+ end
+end
388 themes/pig/chance/lib/slicedice.rb
@@ -0,0 +1,388 @@
+# The Slicer object takes a set of images and slices them as needed, producing a set of images
+# located in a hierarchy (for debugging purposes) in the output directory.
+# The name will be: (output)/path/to/image.png_slice_rect_here.png
+require 'rubygems'
+require 'RMagick'
+require 'FileUtils'
+require 'pp'
+
+class Slicer
+ attr_accessor :images
+
+ def initialize(config)
+ @output_dir = config[:output]
+ @optimization_limit = config[:optimization_limit]
+ end
+
+ # slice performs the slicing operations, putting the images in the output directory
+ def slice
+ image_set = []
+ @images.each do |key, definition|
+ path = definition[:path]
+
+ x, y, width, height = 0, 0, 0, 0
+ if definition[:rect].length > 0
+ x, y, width, height = definition[:rect]
+ end
+
+
+ print "Processing " + path + "...\n"
+
+ begin
+ images = Magick::ImageList.new(path)
+ if images.length < 1
+ print "Could not open; length: ", images.length, "\n"
+ next
+ end
+ rescue
+ print "Could not open the file.\n"
+ next
+ end
+
+ image = images[0]
+
+ image_width, image_height = image.columns, image.rows
+ if x < 0 then x = image_width + x end
+ if y < 0 then y = image_height + y end
+ if width == 0 then width = image_width - x end
+ if height == 0 then height = image_height - y end
+
+ # Crop image
+ result = image.crop(x, y, width, height)
+
+ # Write image: Skipped because we never read it back from disk
+ # Besides, not good to put it in resources/, where it will get added to the build.
+ # FileUtils.mkdir_p @output_dir + "slices/" + File.dirname(path)
+ # slice_path = @output_dir + "slices/" + path + "_" + [x, y, width, height].join("_") + ".png"
+ # result.write(slice_path)
+
+ # update definition
+ definition[:width] = width
+ definition[:height] = height
+ definition[:key] = key
+ definition[:image] = result
+
+ # add to new images collection
+ image_set << definition
+ end
+
+ @image_list = image_set
+ end
+
+ # dice seems like it should continue that, but I just named it dice for fun. It really sprites things.
+ def dice
+ # For each target, for each try, plan out normal, x-repeat, and y-repeat.
+ # Write out images for each target named target.png, target-x.png, and target-y.png.
+
+ # Each "try" is a set of settings with which to attempt to generate a plan.
+ # The wasted space that is returned with the plan is used to determine which try to use.
+ # The spriter will usually try a x-repeat with the normal images first, then separate.
+ # A certain amount of leeway should be given when X-repeat and normal are combined (an amount
+ # of wasted space past which it would be impractical to keep them in the same file)
+ #
+ # Settings work as follows: an aim parameter specifies a multiple of a) the image width
+ # b) the least common multiplier of all repeat pattern widths.
+ tries = []
+ 10.times {|i| tries << {:aim=>i + 1} }
+
+ images = @image_list
+
+ sprites = images.select {|v| not v[:nosprite] }
+ nonsprites = images.select {|v| v[:nosprite] }
+
+ ximages = sprites.select {|v| v[:repeat] == "repeat-x" }
+ yimages = sprites.select {|v| v[:repeat] == "repeat-y" }
+ nimages = sprites.select {|v| v[:repeat] == "no-repeat" }
+
+ plans = []
+ tries.each {|try|
+ # we will have either 2 or three images in any case. So,
+ # we need to pick the best case: the smallest possible primary image.
+ plans << [self.plan(ximages + nimages, try), self.yplan(yimages)]
+ plans << [self.plan(nimages, try), self.plan(ximages, try), self.yplan(yimages)]
+
+ }
+
+ # sort by wasted space. The least wasted is the one we want.
+ plans.sort! {|a, b|
+ total_wasted_a = 0
+ total_wasted_b = 0
+ a.each {|e| total_wasted_a += e[:wasted] }
+ b.each {|e| total_wasted_b += e[:wasted] }
+
+ total_wasted_a -= @optimization_limit if a.length < 3
+ total_wasted_b -= @optimization_limit if b.length < 3
+ total_wasted_a <=> total_wasted_b
+ }
+
+ # Best plan is plan 0.
+ planset = plans[0]
+ new_image_hash = {}
+ total_wasted = 0
+ i = 0
+
+ FileUtils.mkdir_p @output_dir + "images/"
+
+ # Write static
+ nonsprites.each {|nonsprite|
+ i += 1
+ filename = i.to_s + ".png"
+ nonsprite[:sprite_path] = "images/" + filename
+ new_image_hash[nonsprite[:key]] = nonsprite
+ nonsprite[:image].write(@output_dir + "images/" + filename)
+ }
+
+ # Write plan
+ planset.each {|plan|
+ if not (plan and plan[:width] and plan[:width] > 0)
+ next
+ end
+ target_image = Magick::Image.new(plan[:width], plan[:height]) {
+ self.background_color = "transparent"
+ }
+
+ i += 1
+ filename = i.to_s + ".png"
+
+ plan[:plan].each {|image|
+ cols = image[:image].columns
+ rows = image[:image].rows
+ written_x = 0
+ written_y = 0
+
+ image[:sprite_path] = "images/" + filename
+ image[:sprite_x] = image[:x] # just to be as specific as possible
+ image[:sprite_y] = image[:y]
+ new_image_hash[image[:key]] = image
+
+ # loop through plan
+ while written_y < image[:height] do
+ while written_x < image[:width] do
+ target_image.composite!(image[:image], image[:x] + written_x, image[:y] + written_y, Magick::CopyCompositeOp)
+ written_x += [cols, image[:width]].min
+ end
+ written_y += [rows, image[:height]].min
+ end
+ }
+
+ target_image.write(@output_dir + "images/" + filename)
+ total_wasted += plan[:wasted]
+ }
+
+ print "Wasted pixels: ", total_wasted, "\n"
+ @images = new_image_hash
+ end
+
+ # Plan the y-repeat images
+ def yplan(images)
+ # we go in direction: settings[:direction]. We sort the images first, biggest to smallest
+ # based on their directional size (i.e. width for horizontal).
+ # the first image in the sorted set is used to figure out the width or height of the image
+ # (also using the config's units prop)
+ wasted_pixels = 0
+ plan = [] # images
+
+ # Handle no images
+ if images.length < 1
+ return {:wasted=>0, :plan=>plan}
+ end
+
+ # sort images
+ images = images.sort {|a, b|
+ b[:width] <=> a[:width]
+ }
+
+ lcm = 1
+ images.each {|image|
+ lcm = lcm.lcm image[:height]
+ }
+
+
+ x = 0
+ total_height = lcm
+
+ # loop through images
+ images.each {|image|
+ width = image[:width]
+ height = image[:height]
+
+ img = image.dup
+
+ # Set position
+ img[:x] = x
+ img[:y] = 0
+
+ # handle repeated images
+ img[:height] = total_height
+ wasted_pixels += (total_height - height) * width
+
+ # width!
+ img[:width] = width
+
+ # add to plan
+ plan << img
+
+
+ x += img[:width]
+ }
+
+ return {:plan=>plan, :width => x, :height => total_height, :wasted=>wasted_pixels}
+ end
+
+ # Settings={:direction=>}
+ # Returns: {:wasted=>percent, :plan=>collection of clones of image hashes w/plan setings }
+ # Wasted is the amount of a) empty space and b) extra space used by repeating patterns.
+ # The width of the image is either a) the width of the
+ def plan(images, settings)
+ # the first image in the sorted set is used to figure out the width or height of the image
+ wasted_pixels = 0
+ plan = [] # images
+
+ # Handle no images
+ if images.length < 1
+ return {:wasted=>0, :plan=>plan}
+ end
+
+ # sort images
+ images = images.sort {|a, b|
+ res = b[:repeat] <=> a[:repeat] # keep non-repeats together (at begin).
+ if res == 0
+ res = b[:width] <=> a[:width]
+ if res == 0
+ res = b[:height] <=> a[:height] # sort these to get like ones together
+ end
+ end
+ res
+ }
+
+ # Select images
+ normal_images = images.select {|i|
+ i[:anchor] == :none
+ }
+ anchor_left_images = images.select {|i|
+ i[:anchor] == :left
+ }
+ anchor_right_images = images.select {|i|
+ i[:anchor] == :right #or i[:clear] this was causing duplication if a left anchored image was also cleared :)
+ }
+
+ max = 0
+ lcm = 1
+ images.each {|image|
+ max = [max, image[:width]].max
+ if image[:repeat] == "repeat-x"
+ lcm = lcm.lcm image[:width]
+ end
+ }
+
+ # get unit (row/col) size
+ unit_size = max
+ unit_size = unit_size.lcm lcm
+
+ total_width = unit_size * settings[:aim] # 1 is probably best... but we try many :)
+ x =