Created
August 5, 2011 04:53
-
-
Save donpark/1126931 to your computer and use it in GitHub Desktop.
Rough first cut of integration-friendly layout library in coffeescript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
### | |
Rewrite of Bram Stein's [jLayout](http://www.bramstein.com/projects/jlayout/) | |
which uses [new BSD license](http://www.bramstein.com/licenses/BSD.txt). | |
jLayout license summary: | |
jLayout is completely free for commercial and non-commercial use and | |
you can do with it whatever you want, except claim it as your own work. | |
--- | |
jLayout Grid Layout - JavaScript Layout Algorithms v0.41 | |
Licensed under the new BSD License. | |
Copyright 2008-2009, Bram Stein | |
All rights reserved. | |
--- | |
### | |
scope = exports ? this.Layout ?= {} | |
validateBounds = (value) -> | |
if value? | |
{ x, y, width, height } = value | |
if x? and y? and width? and height? | |
value | |
else | |
throw new Error "invalid bounds: #{value}" | |
validateSize = (value) -> | |
if value? | |
{ width, height } = value | |
if width? and height? | |
value | |
else | |
throw new Error "invalid size: #{value}" | |
validateInsets = (value) -> | |
if value? | |
{ top, right, bottom, left } = value | |
if top? and right? and bottom? and left? | |
value | |
else | |
throw new Error "invalid insets: #{value}" | |
### | |
LayoutAdapter is used to integrate layout system with target component system. | |
### | |
scope.Adapter = class LayoutAdapter | |
### | |
Gets or sets the component's position and size as a value object with properties x, y, | |
width and height. All properties should be present in the returned object. | |
### | |
bounds: (component, value) -> | |
if value? | |
value = validateBounds value | |
component.bounds = value if value? | |
else | |
component.bounds | |
### | |
Returns the component's preferred size (i.e. the size it wishes to have) as an object | |
with properties width and height. | |
This is only a hint, there is no guarantee the component will get the size it prefers. | |
### | |
preferredSize: (component) -> validateSize component.preferredSize | |
### | |
Returns the component's minimum size (i.e. the size it should at least have) as an object | |
with properties width and height. | |
### | |
minimumSize: (component) -> validateSize component.minimumSize | |
### | |
Returns the component's maximum size (i.e. the size it should stay below or equal to) | |
as an object with properties width and height. | |
### | |
maximumSize: (component) -> validateSize component.maximumSize | |
### | |
Returns true if the component is visible and should be taken into account when calculating | |
the layout. Returns false otherwise. | |
### | |
isVisible: (component) -> component.visible | |
### | |
Returns the offset between a container and its contents as an object with properties: | |
top, bottom, left, and right. | |
### | |
insets: (component) -> validateInsets component.insets | |
### | |
Calls the layout method on the layout algorithm used to lay out the component (container) | |
it is called on. If the component is not a container (and does not have a layout algorithm) | |
this method can be left empty. | |
### | |
doLayout: (component) -> component.doLayout() | |
scope.Algorithm = class LayoutComponent | |
### | |
Returns the preferred size of the container and its children according to the layout | |
algorithm. | |
### | |
preferred: (container, adapter) -> | |
### | |
Returns the minimum size the container and its children are allowed to have according to the layout algorithm. | |
### | |
minimum: (container, adapter) -> | |
### | |
Returns the maximum size the container and its children are allowed to have according to the layout algorithm. | |
### | |
maximum: (container, adapter) -> | |
### | |
Performs the layout according to the algorithm; resizing and positioning children if necessary. | |
### | |
layout: (container, adapter) -> | |
scope.Manager = class LayoutManager | |
constructor: (@adapter, @layouts = BuiltinLayouts) -> | |
getLayout: (name) -> | |
@layouts[name] | |
hasLayout: (name) -> | |
@layouts[name]? | |
addLayout: (name, clazz) -> | |
@layouts[name] = clazz | |
buildLayout: (name, options) -> | |
clazz = @layouts[name] | |
if clazz? | |
new clazz(options) | |
else | |
throw new Error "unknown layout: #{name}" | |
applyLayout: (component, layoutOrName, options) -> | |
layout = layoutOrName | |
layout = @buildLayout(layout, options) if typeof layout is 'string' and options? | |
layout.layout component, @adapter | |
scope.builtins = BuiltinLayouts = | |
grid: GridLayout | |
scope.Grid = class GridLayout extends LayoutAlgorithm | |
constructor: (options) -> | |
@hgap = options.hgap ? 0 | |
@vgap = options.vgap ? 0 | |
# initialize the number of columns to the number of items | |
# we're laying out. | |
@items = options.items ? [] | |
@columns = options.columns ? @items.length | |
@rows = options.rows ? 0 | |
@fillVertical = options.fill and options.fill is 'vertical' | |
if @rows > 0 | |
@columns = Math.floor((@items.length + @rows - 1) / @rows) | |
else | |
@rows = Math.floor((@items.length + @columns - 1) / @columns) | |
preferred: @__typeLayout('preferred') | |
minimum: @__typeLayout('minimum') | |
maximum: @__typeLayout('maximum') | |
layout: (container, adapter) -> | |
insets = adapter.insets(container) | |
bounds = adapter.bounds(container) | |
x = insets.left | |
y = insets.top | |
width = (bounds.width - (insets.left + insets.right) - (@columns - 1) * @hgap) / @columns | |
height = (bounds.height - (insets.top + insets.bottom) - (@rows - 1) * @vgap) / @rows | |
i = j = 0 | |
items = if typeof @items is 'function' then @items() else @items | |
while i < items.length | |
adapter.bounds items[i], { x: x, y: y, width: width, height: height } | |
if not @fillVertical | |
if j >= @columns | |
y += height + @vgap | |
x = insets.left | |
j = 0 | |
else | |
x += width + @hgap | |
else | |
if j >= @rows | |
x += width + @hgap | |
y = insets.top | |
j = 0 | |
else | |
y += height + @vgap | |
adapter.doLayout items[i] | |
i++ | |
j++ | |
container | |
__typeLayout: (type) -> | |
(container, adapter) -> | |
width = 0 | |
height = 0 | |
type_size = undefined | |
items = if typeof @items is 'function' then @items() else @items | |
insets = adapter.insets(container) | |
i = 0 | |
while i < items.length | |
type_size = adapter["#{type}Size"](items[i]) | |
width = Math.max(width, type_size.width) | |
height = Math.max(height, type_size.height) | |
i++ | |
{ | |
width: insets.left + insets.right + @columns * width + (@columns - 1) * @hgap | |
height: insets.top + insets.bottom + @rows * height + (@rows - 1) * @vgap | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment