app/thumbnails/ContainerThumbnails.scala (186 lines of code) (raw):

package thumbnails import slices._ case class Rectangle(x: Double, y: Double, width: Double, height: Double) class ContainerThumbnails(val fixedContainers: FixedContainers) { val SliceHeight = 40 // if rows are not stretched to accommodate another column val BaseRowHeight = 7 val Width = 100 val Style = "fill: #CCCCCC; stroke: rgba(255,255,255,.75); stroke-width: 1" def totalSpan(columns: Seq[Column]) = columns.map(_.colSpan).sum def sliceHeight(slice: Slice) = { slice.layout.columns match { case Rows(_, _, rows, _) +: Nil => (rows * (BaseRowHeight + 1)) + 1 case _ => SliceHeight } } def summing[A](as: Seq[A])(f: A => Double) = as.inits.toSeq.reverse map { xs => xs.map(f).sum } def drawSlice(slice: Slice, yPos: Double) = { val columns = slice.layout.columns val totalColSpan = totalSpan(columns) val unitSpanWidth = Width.toDouble / totalColSpan val columnLeftSides = summing(columns)(_.colSpan * unitSpanWidth) ((columns zip columnLeftSides) map { case (column, x) => drawColumn( column, x, yPos, column.colSpan * unitSpanWidth, sliceHeight(slice) ) }).flatten } def drawImage( x: Double, y: Double, width: Double, height: Double, itemClasses: ItemClasses ) = { lazy val third = (width - 2) / 3 ((itemClasses.tablet match { case cards.MediaList => Some(Rectangle(x + 1, y + 1, width * .3, height)) case cards.ThreeQuarters | cards.FullMedia75 => Some(Rectangle(x + 1 + third, y + 1, third * 2, height - 2)) case cards.ThreeQuartersRight => Some(Rectangle(x + 1, y + 1, third * 2, height - 2)) case cards.ThreeQuartersTall => Some(Rectangle(x + 1, y + 1, width - 2, height * .8)) case cards.Standard => Some(Rectangle(x + 1, y + 1, width - 2, height * .4)) case cards.Half => Some(Rectangle(x + 1, y + 1, width - 2, height * .7)) case cards.Third => Some(Rectangle(x + 1, y + 1, width - 2, height * .4)) case cards.FullMedia100 => Some(Rectangle(x + 1, y + 1, width - 2, height * .6)) case _ => None }) map { rectDef => <rect x={rectDef.x.toString} y={rectDef.y.toString} width={rectDef.width.toString} height={rectDef.height.toString} style="fill: #BBBBBB; stroke-width: 0" /> }).toSeq } def drawColumn( column: Column, x: Double, y: Double, width: Double, height: Double ) = { def box = <rect x={x.toString} y={y.toString} width={ width.toString } height={height.toString} style={Style} /> def image(itemClasses: ItemClasses) = drawImage(x, y, width, height, itemClasses) column match { case SingleItem(_, itemClasses) => <g> {box} {image(itemClasses)} </g> case Rows(_, columns, rows, itemClasses) => val rowWidth = width / columns.toDouble val rowHeight = height / rows.toDouble for { col <- 0 until columns row <- 0 until rows } yield { val left = x + col * rowWidth val top = y + row * rowHeight <g> <rect x={left.toString} y={top.toString} width={rowWidth.toString} height={rowHeight.toString} style={Style}/> {drawImage(left, top, rowWidth, rowHeight, itemClasses)} </g> } case _: MPU => val centreX = x + width / 2 val centreY = y + height / 2 <g> {box} <text x={centreX.toString} y={centreY.toString} text-anchor="middle" style="font: 10px Arial, Verdana, sans-serif; alignment-baseline: central; fill: white;">MPU</text> </g> case SplitColumn(_, _, topItemClasses, _, bottomItemClasses) => val centreY = y + height / 2 <g> <rect x={x.toString} y={y.toString} width={width.toString} height={(height / 2).toString} style={Style} /> {drawImage(x, y, width, height / 2, topItemClasses)} <rect x={x.toString} y={centreY.toString} width={width.toString} height={(height / 4).toString} style={Style} /> {drawImage(x, centreY, width, height / 4, bottomItemClasses)} <rect x={x.toString} y={(centreY + height / 4).toString} width={width.toString} height={(height / 4).toString} style={Style} /> { drawImage( x, centreY + height / 4, width, height / 4, bottomItemClasses ) } </g> } } def fromId(id: String) = { val maybeSlices = id match { case "dynamic/fast" => Some(Seq(HalfQuarterQl2Ql4)) case "dynamic/slow" => Some(Seq(HalfHl4)) case "dynamic/package" => Some(Seq(ThreeQuarterTallQuarter2)) case "dynamic/slow-mpu" => Some(Seq(HalfHalf, Hl3Mpu)) case "fixed/small/slow-V-half" => Some(Seq(HalfHl4)) case "flexible/general" => Some(Seq(FullMedia75, HalfHl3)) case "flexible/special" => Some(Seq(FullMedia75, QuarterQuarterQuarterQuarter)) case "scrollable/highlights" => Some(Seq(ScrollableHighlightsSlice)) case "scrollable/small" => Some(Seq(ScrollableSmallSlice)) case "scrollable/medium" => Some(Seq(ScrollableMediumSlice)) case "scrollable/feature" => Some(Seq(ScrollableFeatureSlice)) case _ => fixedContainers.unapply(Some(id)).map(_.slices) } maybeSlices map { slices => val yPositions = summing(slices)(sliceHeight) <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width={Width.toString} height={slices.map(sliceHeight).sum.toString}> {slices.zip(yPositions).map((drawSlice _).tupled)} </svg> } } }