JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
Fix db_get_value after mysql->mysqli upgrade
[wfpl.git] / stylus_helpers.styl
index 4a351fa..93b3ae3 100644 (file)
@@ -1,4 +1,7 @@
-// Copyright 2014 Jason Woofenden -- Public Domain (CC0)
+// This program is in the public domain within the United States. Additionally,
+// we waive copyright and related rights in the work worldwide through the CC0
+// 1.0 Universal public domain dedication, which can be found at
+// http://creativecommons.org/publicdomain/zero/1.0/
 
 // This file contains helpers for using stylus in your project.
 //
@@ -54,12 +57,33 @@ dimensions(w, h)
        width: _px(w)
        height: _px(h)
 
+// generate (return) a "calc(Fpx + R%)" to scale linearly with parent
+// args:
+//   pb: pixel width of parent (when biggest)
+//   ps: pixel width of parent (when smallest)
+//   cb: pixel width of child (when biggest)
+//   cs: pixel width of child (when smallest)
+linear_scale_calc(pb, ps, cb, cs)
+       // math explained: (figuring out F and R in calc(Fpx + R%))
+       // f + r * pb = cb   // known_1: formula(pb) -> cb
+       //     f = cb - r * pb    // solve_for_f: subtract (r * pb) from both sides
+       // f + r * ps = cs   // known_2: formula(pb) -> cb
+       //     cb - r * pb + r * ps = cs    // substitute solved_for_f into known_2
+       //     -r * pb + r * ps = cs - cb   // subtract cb from both sides
+       //     r * (-pb + ps) = cs - cb     // factor out r from left side
+       //     r = (cs - cb) / (-pb + ps)   // divide both sides by (-pb + ps)
+       //     r = (cb - cs) / (pb - ps)    // multiply left side by -1/-1
+       r = (cb - cs) / (pb - ps)
+       f = cb - r * pb
+       unquote("calc(" + unit(f, "px") + ' + ' + unit(100 * r, "%") + ")")
+
 // Make children of this element inline-blocks that are spaced evenly accross.
 //
 // To create a minimum distance between: don't use word-spacing, it's broken in
 // firefox. Instead, try padding on the children and negative margin on the
 // parent.
-space_evenly(line_height = 1.2em)
+space_evenly(line_height = 1.2)
+       line_height = unit(line_height, em)
        text-align: justify
        & > *
                display: inline-block
@@ -91,11 +115,15 @@ space_evenly(line_height = 1.2em)
 //         </nav>
 //     styl:
 //         nav li
-//             sprite_rollover "images/nav.png" 150 35 2
+//             sprites_rollover "images/nav.png" 150 35 2
 sprites_rollover(image, width, height, count, v_offset = 0, h_offset = 0)
        width: unit(width, px)
        height: unit(height, px)
-       background-image: url(image)
+       if image[1]
+               background: url(image[0])
+               background: url(image[1]), linear-gradient(transparent, transparent);
+       else
+               background-image: url(image)
        background-position: top left
        background-repeat: no-repeat;
        for n in (0...count)
@@ -110,7 +138,11 @@ sprite_rollover(image, width, height, v_offset = 0, h_offset = 0)
 // see sprites_rollover
 sprites(image, height, count, v_offset = 0, h_offset = 0)
        height: unit(height, px)
-       background-image: url(image)
+       if image[1]
+               background: url(image[0])
+               background: url(image[1]), linear-gradient(transparent, transparent);
+       else
+               background-image: url(image)
        background-position: top left
        background-repeat: no-repeat;
        for n in (0...count)
@@ -118,7 +150,14 @@ sprites(image, height, count, v_offset = 0, h_offset = 0)
                        y = - (@height * n) - unit(v_offset, px)
                        background-position: (0 - unit(h_offset, px)) y
 sprite(image, height, v_offset = 0, h_offset = 0)
-       sprites(image, height, 1, v_offset, h_offset)
+       height: unit(height, px)
+       if image[1]
+               background: url(image[0])
+               background: url(image[1]), linear-gradient(transparent, transparent);
+       else
+               background-image: url(image)
+       background-repeat: no-repeat;
+       background-position: (0 - unit(h_offset, px)) (0 - unit(v_offset, px))
 
 // Styling for a variable height element with an image background where the
 // middle repeats vertically. You must split your image into three images, and
@@ -208,3 +247,317 @@ noselect()
        -moz-user-select: none
        -ms-user-select: none
        user-select: none
+
+// Do all the crazy math to get columns to fit properly
+// and (optionally) scale for responsive designs.
+//
+// example:
+//
+//     columns = wfpl_columns({
+//             type: 'node',
+//             name: 'centerer',
+//             margin: 15px,
+//             width: 940px,
+//             child: {
+//                     type: 'node',
+//                     name: 'middler',
+//                     border-width: 1px,
+//                     border-style: solid,
+//                     border-color: black,
+//                     padding-left: 30px,
+//                     padding-right: 18px,
+//                     child: {
+//                             type: 'alternatives',
+//                             full: {
+//                                     type: 'series',
+//                                     nav: {
+//                                             type: 'node'
+//                                             width: 219px
+//                                             float: left
+//                                     },
+//                                     main: {
+//                                             type: 'node'
+//                                             margin-left: 48px,
+//                                             width: 623px
+//                                             float: left
+//                                     }
+//                             },
+//                             with_sidebar: {
+//                                     type: 'series',
+//                                     nav: {
+//                                             type: 'node',
+//                                             width: 219px
+//                                             float: left
+//                                     },
+//                                     main: {
+//                                             type: 'node',
+//                                             margin-left: 48px,
+//                                             width: 343px
+//                                             float: left
+//                                     },
+//                                     sidebar: {
+//                                             type: 'alternatives',
+//                                             plain: {
+//                                                     type: 'node'
+//                                                     margin-left: 30px,
+//                                                     width: 250px
+//                                                     float: left
+//                                             },
+//                                             bordered: {
+//                                                     type: 'node'
+//                                                     margin-left: 30px,
+//                                                     border-width: 1px,
+//                                                     border-style: solid
+//                                                     border-color: red
+//                                                     padding: 15px,
+//                                                     width: 218px
+//                                                     float: left
+//                                             }
+//                                     }
+//                             }
+//                     }
+//             }
+//     })
+// output css from column calculations
+//     for selector, css in columns.css
+//             body > {selector}
+//                     {css}
+//     @media screen and (max-width: (columns.width))
+//             // output responsive css from column calculations
+//             for selector, css in columns.responsive_css
+//                     body > {selector}
+//                             {css}
+//
+//             // as big as it can be
+//             body > .centerer
+//                     width: auto
+//
+//             // make sure that borders (which won't scale) and rounding errors don't
+//             // break the layout
+//             body > .centerer.full > .main,
+//             body > .centerer.with_sidebar > .sidebar.plain,
+//             body > .centerer.with_sidebar > .sidebar.bordered
+//                     margin-right: -10px
+//
+//     // switch to 1-column layout
+//     @media screen and (max-width: (single_column_max))
+//             for selector, css in columns.css
+//                     body > {selector}
+//                             if selector == '.centerer'
+//                                     margin-top: 0
+//                             else if selector in hide_in_one_column_mode
+//                                     display: none
+//                             else
+//                                     border: none
+//                                     float: none
+//                                     width: auto
+//                                     margin: 0
+//                                     padding: 0
+//                                     margin-top: columns['responsive_css']['.centerer']['margin']
+wfpl_columns_helper(top, node, selector, parent_width, expected_width)
+       css_rules = {}
+       responsive_css_rules = {}
+       if node.type == 'node'
+               left_width = 0px
+               right_width = 0px
+               width = null // inner
+               outer_width = null
+               if node.name
+                       if selector == ''
+                               selector = '.' + node.name
+                       else
+                               selector += ' > .' + node.name
+               // callculate width and outer_width
+               for k, v in node
+                       if match('^(margin|padding|border-width)$', k)
+                               left_width += v
+                               right_width += v
+                       if match('^((margin|padding)-left)|(border-left-width)$', k)
+                               left_width += v
+                       if match('^((margin|padding)-right)|(border-right-width)$', k)
+                               right_width += v
+                       if k != 'type' && k != 'child' && k != 'name' && k != 'outer_width'
+                               css_rules[k] = v
+               for k, v in node
+                       if k == 'width'
+                               width = v
+                       if k == 'outer_width'
+                               outer_width = v
+                               css_rules['width'] = v - left_width - right_width
+               if (!width) && (!outer_width)
+                       if parent_width
+                               outer_width = parent_width
+                       else
+                               error("Couldn't figure out width of " + selector);
+               if outer_width && !width
+                       width = outer_width - left_width - right_width
+               if width && !outer_width
+                       outer_width = width + left_width + right_width
+               unless parent_width // should only happen at top level
+                       parent_width = outer_width
+               top['css'][selector] = css_rules
+               for k, v in css_rules
+                       if k != 'border-width' && k != 'border-left-width' && k != 'border-right-width'
+                               if typeof(v) == 'unit' && unit(v) == 'px'
+                                       responsive_css_rules[k] = floor(unit((v / parent_width) * 100, '%'), 4)
+               top['responsive_css'][selector] = responsive_css_rules
+               if 'child' in node
+                       child_width = wfpl_columns_helper(top, node.child, selector, width, width)
+                       _columns_recurser(rargs)
+                       if child_width != width
+                               error(selector + " is the is wrong width. Expected: " + width + ", got: " + rargs.ret.w)
+               top['widths'][selector] = width
+               return outer_width
+       if node.type == 'alternatives'
+               for name, child in node
+                       if name != 'type'
+                               child_selector = selector + '.' + name
+                               child_width = wfpl_columns_helper(top, child, child_selector, parent_width, expected_width)
+                               if expected_width
+                                       if child_width != expected_width
+                                               error(child_selector + " is wrong width. Expected: " + expected_width + ", got: " + child_width)
+                               else
+                                       expected_width = child_width
+                               unless parent_width
+                                       parent_width = child_width
+               return expected_width
+       if node.type == 'series'
+               series_width = 0px
+               for name, child in node
+                       if name != 'type'
+                               if selector == ''
+                                       child_selector = '.' + name
+                               else
+                                       child_selector = selector + ' > .' + name
+                               child_width = wfpl_columns_helper(top, child, child_selector, parent_width, null)
+                               series_width += child_width
+               if expected_width
+                       if series_width != expected_width
+                               error(selector + " series total wrong. expected: " + expected_width + ", got: " + series_width);
+               return series_width
+       return // never reached with valid data
+
+wfpl_columns(hash)
+       top = { // contains settings, and is returned from porcelain
+               responsive: responsive
+               width: null
+               css: {}
+               responsive_css: {}
+               widths: {}
+       }
+       top.width = wfpl_columns_helper(top, hash, '', null, null)
+       return top
+
+// internal helper
+_pct_pos(offset, width, sheet_width)
+       if width is sheet_width
+               return 0
+       ratio = offset / (sheet_width - width)
+       if ratio is 0
+               return 0
+       return unit(ratio * 100, '%')
+
+// wfpl_spritesheets documentation/example:
+//
+// wfpl_spritesheets({
+//   main: {
+//     image: sha1['images/sprites.min.svg']
+//     y_origin: 'bottom' // invert y coordinates below (thx inkscape)
+//     w: 200             // width of document (whatever units)
+//     h: 400             // height of document (whatever units)
+//     sprites: {
+//       ".icon": {
+//         y: 399         // top (regardless of y_origin)
+//         w: 48          // width of sprite (in doc coords)
+//         h: 48          // height of sprite (in doc coords)
+//         gap: 2         // space _between_ rows/columns
+//         hover: 'right' // can be 'right' or 'down', meaning: direction to
+//                        // move in spritesheet to find the ":hover" graphic
+//         column: 'three' 'four' 'five' 'six' // key can be 'column' or 'row'
+//               // values are postfix for class names
+//       }
+//       ".logo": {
+//         y: 100
+//         w: 200
+//         h: 50
+//       }
+//     }
+//   }
+// })
+//
+// HTML:
+//   <div class="logo"></span>
+//   <span class="icon icon_three"></span>
+//   <span class="icon icon_four"></span>
+//
+// CSS:
+//   .icon
+//     display: inline-block
+//     width: 10%
+wfpl_spritesheets(hash)
+       for unused_name, sheet in hash
+               for selector, args in sheet.sprites
+                       if sheet.y_origin is 'bottom'
+                               y = sheet.h - args.y
+                       else
+                               y = args.y
+                       if args.x
+                               x = args.x
+                       else
+                               x = 0
+                       if args.gap
+                               gap = args.gap
+                       else
+                               gap = 0
+                       w = args.w
+                       h = args.h
+                       if args.hover is 'right'
+                               hdx = args.w + gap
+                               hdy = 0
+                       else if args.hover is 'down'
+                               hdx = 0
+                               hdy = args.h + gap
+                       if not (args.column or args.row)
+                               selector_before = selector + ":before"
+                               {selector_before}
+                                       content: ''
+                                       display: block
+                                       background-position: _pct_pos(x, w, sheet.w) _pct_pos(y, h, sheet.h)
+                                       background-image: url((sheet['image']))
+                                       background-size: unit(sheet['w'] / w * 100, '%') auto
+                                       background-repeat: no-repeat
+                                       padding-top: unit(h / w * 100, '%')
+                               selector_hover_before = selector + ":hover:before"
+                               if args.hover
+                                       {selector_hover_before}
+                                               background-position: _pct_pos(x + hdx, w, sheet.w) _pct_pos(y + hdy, h, sheet.h)
+                       else
+                               names = ()
+                               if args.column
+                                       dx = 0
+                                       dy = args.h + gap
+                                       for n in args.column
+                                               push(names, n)
+                               else if args.row
+                                       dx = args.w + gap
+                                       dy = 0
+                                       for n in args.row
+                                               push(names, n)
+                               d = 0
+                               for postfix in names
+                                       selector_n_before = selector + "_" + postfix + ":before"
+                                       {selector_n_before}
+                                               background-position: _pct_pos(x + d * dx, w, sheet.w) _pct_pos(y + d * dy, h, sheet.h)
+                                       if args.hover
+                                               selector_n_hover_before = selector + "_" + postfix + ":hover:before"
+                                               {selector_n_hover_before}
+                                                       background-position: _pct_pos(x + hdx + d * dx, w, sheet.w) _pct_pos(y + hdy + d * dy, h, sheet.h)
+                                       d += 1
+                               selector_before = selector + ":before"
+                               {selector_before}
+                                       content: ''
+                                       display: block
+                                       background-image: url((sheet['image']))
+                                       background-size: unit(sheet['w'] / w * 100, '%') auto
+                                       padding-top: unit(h / w * 100, '%')
+       return ret