From ad4a486ea1ea3123e2182c090c17674d38de8d90 Mon Sep 17 00:00:00 2001 From: Chris Eppstein Date: Sun, 21 Feb 2010 22:44:38 -0800 Subject: [PATCH] Simpler gradient output by discarding from() and to() --- .../stylesheets/compass/css3/_gradient.sass | 90 +++++++------ .../functions/gradient_support.rb | 123 ++++++++++++------ .../stylesheets/compass/css/gradients.css | 70 +++++++--- .../stylesheets/compass/sass/gradients.sass | 26 +++- 4 files changed, 212 insertions(+), 97 deletions(-) diff --git a/frameworks/compass/stylesheets/compass/css3/_gradient.sass b/frameworks/compass/stylesheets/compass/css3/_gradient.sass index 526f2bc0..2e874e32 100644 --- a/frameworks/compass/stylesheets/compass/css3/_gradient.sass +++ b/frameworks/compass/stylesheets/compass/css3/_gradient.sass @@ -1,44 +1,58 @@ -=gradient(!type, !coords, !color_start, !color_end, !color_stop = false) - !gradient= "#{!coords}, from(#{!color_start}), to(#{!color_end})" - @if !color_stop - !gradient= !gradient + ", " + !color_stop - background: -webkit-gradient(#{!type}, #{!gradient}) - background: -moz-#{!type}-gradient(#{!gradient}) - -//* - // This will yield a radial gradient with an apparent specular highlight - +radial-gradient("45 45, 10, 52 50, 30", Cyan, DodgerBlue) - -=radial-gradient(!coords, !color1, !color2, !color_stop = false) - +gradient("radial", !coords, !color1, !color2, !color_stop) - -//* - // This yields a linear gradient spanning from top to bottom - +linear-gradient(color_stops(white, black)) - - // This yields a linear gradient spanning from bottom to top - +linear-gradient(color_stops(white, black), "bottom") - - // This yields a linear gradient spanning from left to right - +linear-gradient(color_stops(white, black), "left") - - // This yields a linear gradient starting at white passing - // thru blue at 33% down and then to black - +linear-gradient(color_stops(white, blue 33%, black)) - - // This yields a linear gradient starting at white passing - // thru blue at 33% down and then to black at 67% until the end - +linear-gradient(color_stops(white, blue 33%, black 67%)) +// This yields a linear gradient spanning from top to bottom +// +// +linear-gradient(color_stops(white, black)) +// +// This yields a linear gradient spanning from bottom to top +// +// +linear-gradient(color_stops(white, black), "bottom") +// +// This yields a linear gradient spanning from left to right +// +// +linear-gradient(color_stops(white, black), "left") +// +// This yields a linear gradient starting at white passing +// thru blue at 33% down and then to black +// +// +linear-gradient(color_stops(white, blue 33%, black)) +// +// This yields a linear gradient starting at white passing +// thru blue at 33% down and then to black at 67% until the end +// +// +linear-gradient(color_stops(white, blue 33%, black 67%)) +// +// Browsers Supported: +// +// - Chrome +// - Safari +// - Firefox 3.6 =linear-gradient(!color_stops, !start = "top") // Firefox's gradient api is nice. // Webkit's gradient api sucks -- hence these backflips: !end = grad_opposite_position(!start) - !start_color = grad_start_color(!color_stops) - !end_color = grad_end_color(!color_stops) - !webkit_stops = grad_color_stops(!color_stops) - !gradient= "#{grad_point(!start)}, #{grad_point(!end)}, from(#{!start_color}), to(#{!end_color})" - @if !webkit_stops - !gradient= !gradient + ", " + !webkit_stops - background-image: -webkit-gradient(linear, #{!gradient}) + background-image: -webkit-gradient(linear, #{grad_point(!start)}, #{grad_point(!end)}, #{grad_color_stops(!color_stops)}) background-image: -moz-linear-gradient(#{!start}, #{!color_stops}) + +// Due to limitation's of webkit, the radial gradient mixin works best if you use +// pixel-based color stops. +// +// Examples: +// +// // Defaults to a centered, 100px radius gradient +// +radial-gradient(color_stops(#c00, #00c)) +// // 100px radius gradient in the top left corner +// +radial-gradient(color_stops(#c00, #00c), "top left") +// // Three colors, ending at 50px and passing thru #fff at 25px +// +radial-gradient(color_stops(#c00, #fff, #00c 50px)) +// +// Browsers Supported: +// +// - Chrome +// - Safari +// - Firefox 3.6 + +=radial-gradient(!color_stops, !center_position = "center center") + !end_pos = grad_end_position(!color_stops, true) + background-image: -webkit-gradient(radial, #{grad_point(!center_position)}, 0, #{grad_point(!center_position)}, #{!end_pos}, #{grad_color_stops(!color_stops)}) + background-image: -moz-radial-gradient(#{!center_position}, circle, #{!color_stops}) + diff --git a/lib/compass/sass_extensions/functions/gradient_support.rb b/lib/compass/sass_extensions/functions/gradient_support.rb index 764e679b..34dcbadf 100644 --- a/lib/compass/sass_extensions/functions/gradient_support.rb +++ b/lib/compass/sass_extensions/functions/gradient_support.rb @@ -25,8 +25,11 @@ module Compass::SassExtensions::Functions::GradientSupport s = "#{color}" if stop s << " " - s << stop.times(Sass::Script::Number.new(100)).to_s - s << "%" + if stop.unitless? + s << stop.times(Sass::Script::Number.new(100, ["%"])).to_s + else + s << stop.to_s + end end s end @@ -41,6 +44,7 @@ module Compass::SassExtensions::Functions::GradientSupport when "bottom" then "top" when "left" then "right" when "right" then "left" + when "center" then "center" else raise Sass::SyntaxError, "Cannot determine the opposite of #{pos}" end @@ -50,49 +54,53 @@ module Compass::SassExtensions::Functions::GradientSupport # returns color-stop() calls for use in webkit. def grad_color_stops(color_list) - positions = color_list.values.map{|c| [c.stop && c.stop.value, c.color]} - # fill in the blank positions - positions[0][0] = 0 unless positions.first.first - positions[positions.size - 1][0] = 1 unless positions.last.first - for i in 0...positions.size - if positions[i].first.nil? - num = 2.0 - for j in (i+1)...positions.size - if positions[j].first - positions[i][0] = positions[i-1].first + (positions[j].first - positions[i-1].first) / num - break - else - num += 1 - end + normalize_stops!(color_list) + max = color_list.values.last.stop + color_stops = color_list.values.map do |pos| + # have to convert absolute units to percentages for use in color stop functions. + stop = pos.stop + stop = stop.div(max).times(Sass::Script::Number.new(100,["%"])) if stop.numerator_units == max.numerator_units + "color-stop(#{stop}, #{pos.color})" + end + Sass::Script::String.new(color_stops.join(", ")) + end + + # returns the end position of the gradient from the color stop + def grad_end_position(color_list, radial = Sass::Script::Bool.new(false)) + default = Sass::Script::Number.new(100) + grad_position(color_list, Sass::Script::Number.new(color_list.values.size), default, radial) + end + + def grad_position(color_list, index, default, radial = Sass::Script::Bool.new(false)) + stop = color_list.values[index.value - 1].stop + if stop && radial.to_bool + orig_stop = stop + if stop.unitless? + if stop.value <= 1 + # A unitless number is assumed to be a percentage when it's between 0 and 1 + stop = stop.times(Sass::Script::Number.new(100, ["%"])) + else + # Otherwise, a unitless number is assumed to be in pixels + stop = stop.times(Sass::Script::Number.new(1, ["px"])) end end - end - positions = positions[1..-1] if positions.first.first == 0.0 - positions = positions[0..-2] if positions.last.first == 1.0 - if positions.empty? - # We return false here since sass has no nil. - Sass::Script::Bool.new(false) + if stop.numerator_units == ["%"] && color_list.values.last.stop && color_list.values.last.stop.numerator_units == ["px"] + stop = stop.times(color_list.values.last.stop).div(Sass::Script::Number.new(100, ["%"])) + end + Compass::Logger.new.record(:warning, "Webkit only supports pixels for the start and end stops for radial gradients. Got: #{orig_stop}") if stop.numerator_units != ["px"] + stop.div(Sass::Script::Number.new(1, stop.numerator_units, stop.denominator_units)) + elsif stop + stop else - color_stops = positions.map {|pos| "color-stop(#{Sass::Script::Number.new((pos.first*10000).round/100.0).to_s}%, #{pos.last})"} - Sass::Script::String.new(color_stops.join(", ")) + default end end - # the first color from a list of color stops - def grad_start_color(color_list) - color_list.values.first.color - end - - # the last color from a list of color stops - def grad_end_color(color_list) - color_list.values.last.color - end - # the given a position, return a point in percents def grad_point(position) position = position.value position = if position[" "] - if position =~ /(top|bottom) (left|right)/ + if position =~ /(top|bottom|center) (left|right|center)/ "#{$2} #{$1}" else position @@ -103,9 +111,16 @@ module Compass::SassExtensions::Functions::GradientSupport "left #{position}" when /left|right/ "#{position} top" + else + position end end - Sass::Script::String.new position.gsub(/top/, "0%").gsub(/bottom/, "100%").gsub(/left/,"0%").gsub(/right/,"100%") + Sass::Script::String.new(position. + gsub(/top/, "0%"). + gsub(/bottom/, "100%"). + gsub(/left/,"0%"). + gsub(/right/,"100%"). + gsub(/center/, "50%")) end def color_stops(*args) @@ -125,7 +140,11 @@ module Compass::SassExtensions::Functions::GradientSupport end stop = Sass::Script::Number.new(number) elsif !stop.nil? - raise Sass::SyntaxError, "A color stop location must be between 0 and 1 or 0% and 100%. Got: #{stop}" + number = Sass::Script::Parser.parse(stop, 0, 0) + unless number.is_a?(Sass::Script::Number) + raise Sass::SyntaxError, "A color stop location must be a number. Got: #{stop}" + end + stop = number end ColorStop.new(color, stop) else @@ -133,5 +152,35 @@ module Compass::SassExtensions::Functions::GradientSupport end end) end + private + def normalize_stops!(color_list) + positions = color_list.values + # fill in the start and end positions, if unspecified + positions.first.stop = Sass::Script::Number.new(0) unless positions.first.stop + positions.last.stop = Sass::Script::Number.new(100, ["%"]) unless positions.last.stop + # fill in empty values + for i in 0...positions.size + if positions[i].stop.nil? + num = 2.0 + for j in (i+1)...positions.size + if positions[j].stop + positions[i].stop = positions[i-1].stop.plus((positions[j].stop.minus(positions[i-1].stop)).div(Sass::Script::Number.new(num))) + break + else + num += 1 + end + end + end + end + # normalize unitless numbers + positions.each do |pos| + if pos.stop.unitless? && pos.stop.value <= 1 + pos.stop = pos.stop.times(Sass::Script::Number.new(100, ["%"])) + elsif pos.stop.unitless? + pos.stop = pos.stop.times(Sass::Script::Number.new(1, ["px"])) + end + end + nil + end end end diff --git a/test/fixtures/stylesheets/compass/css/gradients.css b/test/fixtures/stylesheets/compass/css/gradients.css index 93f0acf6..dcdf94b0 100644 --- a/test/fixtures/stylesheets/compass/css/gradients.css +++ b/test/fixtures/stylesheets/compass/css/gradients.css @@ -1,43 +1,71 @@ .linear-1 { - background-image: -webkit-gradient(linear, 0% 0%, 100% 0%, from(#dddddd), to(#aaaaaa)); - background-image: -moz-linear-gradient(top, #dddddd, #aaaaaa); } + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%, #dddddd), color-stop(100%, #aaaaaa)); + background-image: -moz-linear-gradient(top, #dddddd 0%, #aaaaaa 100%); } .linear-2 { - background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#dddddd), to(#aaaaaa)); - background-image: -moz-linear-gradient(left, #dddddd, #aaaaaa); } + background-image: -webkit-gradient(linear, 0% 0%, 100% 0%, color-stop(0%, #dddddd), color-stop(100%, #aaaaaa)); + background-image: -moz-linear-gradient(left, #dddddd 0%, #aaaaaa 100%); } .linear-3 { - background-image: -webkit-gradient(linear, 0% 0%, 100% 100%, from(#dddddd), to(#aaaaaa)); - background-image: -moz-linear-gradient(top left, #dddddd, #aaaaaa); } + background-image: -webkit-gradient(linear, 0% 0%, 100% 100%, color-stop(0%, #dddddd), color-stop(100%, #aaaaaa)); + background-image: -moz-linear-gradient(top left, #dddddd 0%, #aaaaaa 100%); } .linear-4 { - background-image: -webkit-gradient(linear, 0% 100%, 100% 0%, from(#dddddd), to(#aaaaaa)); - background-image: -moz-linear-gradient(top right, #dddddd, #aaaaaa); } + background-image: -webkit-gradient(linear, 100% 0%, 0% 100%, color-stop(0%, #dddddd), color-stop(100%, #aaaaaa)); + background-image: -moz-linear-gradient(top right, #dddddd 0%, #aaaaaa 100%); } .linear-5 { - background-image: -webkit-gradient(linear, 0% 0%, 100% 0%, from(#dddddd), to(#aaaaaa), color-stop(50%, #cccccc)); - background-image: -moz-linear-gradient(top, #dddddd, #cccccc, #aaaaaa); } + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%, #dddddd), color-stop(50%, #cccccc), color-stop(100%, #aaaaaa)); + background-image: -moz-linear-gradient(top, #dddddd 0%, #cccccc 50%, #aaaaaa 100%); } .linear-6 { - background-image: -webkit-gradient(linear, 0% 0%, 100% 0%, from(#dddddd), to(#aaaaaa), color-stop(20%, #cccccc)); - background-image: -moz-linear-gradient(top, #dddddd, #cccccc 20%, #aaaaaa); } + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%, #dddddd), color-stop(20%, #cccccc), color-stop(100%, #aaaaaa)); + background-image: -moz-linear-gradient(top, #dddddd 0%, #cccccc 20%, #aaaaaa 100%); } .linear-7 { - background-image: -webkit-gradient(linear, 0% 0%, 100% 0%, from(#dddddd), to(#aaaaaa), color-stop(20%, #cccccc), color-stop(60%, #eeeeee)); - background-image: -moz-linear-gradient(top, #dddddd, #cccccc 20%, #eeeeee, #aaaaaa); } + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%, #dddddd), color-stop(20%, #cccccc), color-stop(50.1%, #eeeeee), color-stop(100%, #aaaaaa)); + background-image: -moz-linear-gradient(top, #dddddd 0%, #cccccc 20%, #eeeeee 50.1%, #aaaaaa 100%); } .linear-8 { - background-image: -webkit-gradient(linear, 0% 0%, 100% 0%, from(#dddddd), to(#aaaaaa), color-stop(80%, #dddddd)); - background-image: -moz-linear-gradient(top, #dddddd 80%, #aaaaaa); } + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(80%, #dddddd), color-stop(100%, #aaaaaa)); + background-image: -moz-linear-gradient(top, #dddddd 80%, #aaaaaa 100%); } .linear-9 { - background-image: -webkit-gradient(linear, 0% 0%, 100% 0%, from(#dddddd), to(#aaaaaa), color-stop(20%, #aaaaaa)); - background-image: -moz-linear-gradient(top, #dddddd, #aaaaaa 20%); } + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%, #dddddd), color-stop(100%, #aaaaaa)); + background-image: -moz-linear-gradient(top, #dddddd 0%, #aaaaaa 20%); } .linear-10 { - background-image: -webkit-gradient(linear, 0% 0%, 100% 0%, from(#dddddd), to(#aaaaaa), color-stop(40%, #dddddd), color-stop(50%, #aaaaaa)); + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(80%, #dddddd), color-stop(100%, #aaaaaa)); background-image: -moz-linear-gradient(top, #dddddd 40%, #aaaaaa 50%); } .linear-11 { - background-image: -webkit-gradient(linear, 0% 0%, 100% 0%, from(#dddddd), to(#aaaaaa), color-stop(40%, #dddddd), color-stop(45%, black), color-stop(50%, #aaaaaa)); - background-image: -moz-linear-gradient(top, #dddddd 40%, black, #aaaaaa 50%); } + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(80%, #dddddd), color-stop(90%, black), color-stop(100%, #aaaaaa)); + background-image: -moz-linear-gradient(top, #dddddd 40%, black 45%, #aaaaaa 50%); } + +.radial-1 { + background-image: -webkit-gradient(radial, 50% 50%, 0, 50% 50%, 100, color-stop(0%, #dddddd), color-stop(100%, #aaaaaa)); + background-image: -moz-radial-gradient(center center, circle, #dddddd 0%, #aaaaaa 100%); } + +.radial-2 { + background-image: -webkit-gradient(radial, 50% 50%, 0, 50% 50%, 100, color-stop(0%, #dddddd), color-stop(100%, #aaaaaa)); + background-image: -moz-radial-gradient(center center, circle, #dddddd 0%, #aaaaaa 100%); } + +.radial-3 { + background-image: -webkit-gradient(radial, 50% 0%, 0, 50% 0%, 100, color-stop(0%, #dddddd), color-stop(100%, #aaaaaa)); + background-image: -moz-radial-gradient(top center, circle, #dddddd 0%, #aaaaaa 100%); } + +.radial-4 { + background-image: -webkit-gradient(radial, 50% 50%, 0, 50% 50%, 100, color-stop(0%, #dddddd), color-stop(100%, #aaaaaa)); + background-image: -moz-radial-gradient(center center, circle, #dddddd 0%, #aaaaaa 100%); } + +.radial-5 { + background-image: -webkit-gradient(radial, 50% 0%, 0, 50% 0%, 100, color-stop(0%, #dddddd), color-stop(100%, #aaaaaa)); + background-image: -moz-radial-gradient(top center, circle, #dddddd 0%, #aaaaaa 100%); } + +.radial-6 { + background-image: -webkit-gradient(radial, 50% 50%, 0, 50% 50%, 50, color-stop(40%, #dddddd), color-stop(100%, #aaaaaa)); + background-image: -moz-radial-gradient(center center, circle, #dddddd 20px, #aaaaaa 50px); } + +.radial-7 { + background-image: -webkit-gradient(radial, 50% 50%, 0, 50% 50%, 50, color-stop(20%, #dddddd), color-stop(100%, #aaaaaa)); + background-image: -moz-radial-gradient(center center, circle, #dddddd 20%, #aaaaaa 50px); } diff --git a/test/fixtures/stylesheets/compass/sass/gradients.sass b/test/fixtures/stylesheets/compass/sass/gradients.sass index 1f729437..43141bf8 100644 --- a/test/fixtures/stylesheets/compass/sass/gradients.sass +++ b/test/fixtures/stylesheets/compass/sass/gradients.sass @@ -21,4 +21,28 @@ .linear-10 +linear-gradient(color_stops(#ddd 40%, #aaa 50%)) .linear-11 - +linear-gradient(color_stops(#ddd 40%, #000, #aaa 50%)) \ No newline at end of file + +linear-gradient(color_stops(#ddd 40%, #000, #aaa 50%)) +.radial-1 + // A default radial gradient: + // A centered gradient having the shape of the container (aka ellipse) + +radial-gradient(color_stops(#ddd, #aaa)) +.radial-2 + // A centered gradient having the shape of the container (aka ellipse) + +radial-gradient(color_stops(#ddd, #aaa)) +.radial-3 + // A centered gradient at the top having the shape of the container (aka ellipse) + +radial-gradient(color_stops(#ddd, #aaa), "top center") +.radial-4 + // A centered gradient having a circular shape + +radial-gradient(color_stops(#ddd, #aaa)) +.radial-5 + // A centered gradient at the top having a circular shape + +radial-gradient(color_stops(#ddd, #aaa), "top center") +.radial-6 + // A centered circular gradient with color stops + // The color stops must be absolute units + +radial-gradient(color_stops(#ddd 20px, #aaa 50px)) +.radial-7 + // A centered elliptical gradient with color stops + // The color stops must be relative units + +radial-gradient(color_stops(#ddd 20%, #aaa 50px))