Skip to content

Instantly share code, notes, and snippets.

@donpark
Created August 5, 2011 04:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save donpark/1126931 to your computer and use it in GitHub Desktop.
Save donpark/1126931 to your computer and use it in GitHub Desktop.
Rough first cut of integration-friendly layout library in coffeescript
###
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