# -*- encoding: utf-8 -*- # # lib/shoes.rb # The Shoes base app, both a demonstration and the learning tool for # using Shoes. # ARGV.delete_if { |x| x =~ /-psn_/ } require 'open-uri' require 'optparse' require 'resolv-replace' if RUBY_PLATFORM =~ /win/ require 'shoes/inspect' require 'shoes/cache' if Object.const_defined? :Shoes require 'shoes/image' end require 'shoes/shybuilder' def Shoes.hook; end class Encoding ASCII_8BIT = 'ASCII-8BIT' end class Range def rand conv = (Integer === self.end && Integer === self.begin ? :to_i : :to_f) ((Kernel.rand * (self.end - self.begin)) + self.begin).send(conv) end end unless Time.respond_to? :today def Time.today t = Time.now t - (t.to_i % 86400) end end class Shoes RELEASES = %w[Curious Raisins Policeman] NotFound = proc do para "404 NOT FOUND, GUYS!" end class << self; attr_accessor :locale, :language end @locale = ENV["SHOES_LANG"] || ENV["LC_MESSAGES"] || ENV["LC_ALL"] || ENV["LANG"] || "C" @language = @locale[/^(\w{2})_/, 1] || "en" @mounts = [] OPTS = OptionParser.new do |opts| opts.banner = "Usage: shoes [options] (app.rb or app.shy)" opts.on("-m", "--manual", "Open the built-in manual.") do show_manual end opts.on("-p", "--package", "Package a Shoes app for Windows, OS X and Linux.") do |s| make_pack end opts.on("-g", "--gem", "Passes commands to RubyGems.") do require 'shoes/setup' require 'rubygems/gem_runner' Gem::GemRunner.new.run(ARGV) raise SystemExit, "" end opts.on("--manual-html DIRECTORY", "Saves the manual to a directory as HTML.") do |dir| manual_as :html, dir raise SystemExit, "HTML manual in: #{dir}" end opts.on("--install MODE SRC DEST", "Installs a file.") do |mode| src, dest = ARGV FileUtils.install src, dest, :mode => mode.to_i(8), :preserve => true raise SystemExit, "" end opts.on_tail("-v", "--version", "Display the version info.") do raise SystemExit, File.read("#{DIR}/VERSION.txt").strip end opts.on_tail("-h", "--help", "Show this message") do raise SystemExit, opts.to_s end end class SettingUp < StandardError; end @setups = {} def self.setup &blk require 'shoes/setup' line = caller[0] return if @setups[line] script = line[/^(.+?):/, 1] set = Shoes::Setup.new(script, &blk) @setups[line] = true unless set.no_steps? raise SettingUp end end def self.show_selector fname = ask_open_file Shoes.visit(fname) if fname end def self.package_app fname = ask_open_file return false unless fname start_shy_builder fname end def self.splash font "#{DIR}/fonts/Lacuna.ttf" Shoes.app :width => 400, :height => 300, :resizable => false do style(Para, :align => "center", :weight => "bold", :font => "Lacuna Regular", :size => 13) style(Link, :stroke => yellow, :underline => nil) style(LinkHover, :stroke => yellow, :fill => nil) x1 = 77; y1 = 122 x2 = 148; y2 = -122 x3 = 245; y3 = 0 nofill strokewidth 40.0 @waves = stack :top => 0, :left => 0 require 'shoes/search' require 'shoes/help' stack :margin => 18 do para "Welcome to", :stroke => "#DFA", :margin => 0 para "SHOES", :size => 48, :stroke => "#DFA", :margin_top => 0 stack do background black(0.2), :curve => 8 para link("Open an App.") { Shoes.show_selector and close }, :margin => 10, :margin_bottom => 4 para link("Package an App.") { Shoes.package_app and close }, :margin => 10, :margin_bottom => 4 para link("Read the Manual.") { Shoes.show_manual and close }, :margin => 10 end inscription "Alt-Slash opens the console.", :stroke => "#DFA", :align => "center" end animate(10) do |ani| a = Math.sin(ani * 0.02) * 20 @waves.clear do background white y = -30 16.times do |i| shape do move_to x = (-300 - (i*(a*0.8))), y c = (a + 14) * 0.01 stroke rgb(i * 0.06, c + 0.1, 0.1, 1.0 - (ani * 0.0003)) 4.times do curve_to x1 + x, (y1-(i*a)) + y, x2 + x, (y2+(i*a)) + y, x3 + x, y3 + y x += x3 end end y += 30 end end end end end def self.make_pack require 'shoes/pack' Shoes.app(:width => 500, :height => 380, :resizable => false, &PackMake) end def self.manual_p(str, path) str.gsub(/\n+\s*/, " "). gsub(/&/, '&').gsub(/>/, '>').gsub(/>/, '<').gsub(/"/, '"'). gsub(/`(.+?)`/m, '\1').gsub(/\[\[BR\]\]/i, "
\n"). gsub(/\^(.+?)\^/m, '\1'). gsub(/'''(.+?)'''/m, '\1').gsub(/''(.+?)''/m, '\1'). gsub(/\[\[(http:\/\/\S+?)\]\]/m, '\1'). gsub(/\[\[(http:\/\/\S+?) (.+?)\]\]/m, '\2'). gsub(/\[\[(\S+?)\]\]/m) do ms, mn = $1.split(".", 2) if mn '' + mn + '' else '' + ms + '' end end. gsub(/\[\[(\S+?) (.+?)\]\]/m, '\2'). gsub(/\!(\{[^}\n]+\})?([^!\n]+\.\w+)\!/) do x = "static/#$2" FileUtils.cp("#{DIR}/#{x}", "#{path}/#{x}") if File.exists? "#{DIR}/#{x}" '' end end def self.manual_link(sect) end TITLES = {:title => :h1, :subtitle => :h2, :tagline => :h3, :caption => :h4} def self.manual_as format, *args require 'shoes/search' require 'shoes/help' case format when :shoes Shoes.app(:width => 720, :height => 640, &Shoes::Help) else extend Shoes::Manual man = self dir, = args FileUtils.mkdir_p File.join(dir, 'static') FileUtils.cp "static/shoes-icon.png", "#{dir}/static" %w[manual.css code_highlighter.js code_highlighter_ruby.js]. each { |x| FileUtils.cp "static/#{x}", "#{dir}/static" } html_bits = proc do proc do |sym, text| case sym when :intro div.intro { p { self << man.manual_p(text, dir) } } when :code pre { code.rb text.gsub(/^\s*?\n/, '') } when :colors color_names = (Shoes::COLORS.keys*"\n").split("\n").sort color_names.each do |color| c = Shoes::COLORS[color.intern] f = c.dark? ? "white" : "black" div.color(:style => "background: #{c}; color: #{f}") { h3 color; p c } end when :index tree = man.class_tree shown = [] i = 0 index_p = proc do |k, subs| unless shown.include? k i += 1 p "▸ #{k}", :style => "margin-left: #{20*i}px" subs.uniq.sort.each do |s| index_p[s, tree[s]] end if subs i -= 1 shown << k end end tree.sort.each &index_p # index_page when :list ul { text.each { |x| li { self << man.manual_p(x, dir) } } } else send(TITLES[sym] || :p) { self << man.manual_p(text, dir) } end end end docs = load_docs(Shoes::Manual::path) sections = docs.map { |x,| x } docn = 1 docs.each do |title1, opt1| subsect = opt1['sections'].map { |x,| x } menu = sections.map do |x| [x, (subsect if x == title1)] end path1 = File.join(dir, title1.gsub(/\W/, '')) make_html("#{path1}.html", title1, menu) do h2 "The Shoes Manual" h1 title1 man.wiki_tokens opt1['description'], true, &instance_eval(&html_bits) p.next { text "Next: " a opt1['sections'].first[1]['title'], :href => "#{opt1['sections'].first[0]}.html" } end optn = 1 opt1['sections'].each do |title2, opt2| path2 = File.join(dir, title2) make_html("#{path2}.html", opt2['title'], menu) do h2 "The Shoes Manual" h1 opt2['title'] man.wiki_tokens opt2['description'], true, &instance_eval(&html_bits) opt2['methods'].each do |title3, desc3| sig, val = title3.split(/\s+»\s+/, 2) aname = sig[/^[^(=]+=?/].gsub(/\s/, '').downcase a :name => aname div.method do a sig, :href => "##{aname}" text " » #{val}" if val end div.sample do man.wiki_tokens desc3, &instance_eval(&html_bits) end end if opt1['sections'][optn] p.next { text "Next: " a opt1['sections'][optn][1]['title'], :href => "#{opt1['sections'][optn][0]}.html" } elsif docs[docn] p.next { text "Next: " a docs[docn][0], :href => "#{docs[docn][0].gsub(/\W/, '')}.html" } end optn += 1 end end docn += 1 end end end def self.show_manual manual_as :shoes end def self.show_log require 'shoes/log' return if @log_app and Shoes.APPS.include? @log_app @log_app = Shoes.app do extend Shoes::LogWindow setup end end def self.mount(path, meth, &blk) @mounts << [path, meth || blk] end SHOES_URL_RE = %r!^@([^/]+)(.*)$! def self.run(path) uri = URI(path) @mounts.each do |mpath, rout| m, *args = *path.match(/^#{mpath}$/) if m unless rout.is_a? Proc rout = rout[0].instance_method(rout[1]) end return [rout, args] end end case uri.path when "/" [nil] when SHOES_URL_RE [proc { eval(URI("http://#$1:53045#$2").read) }] else [NotFound] end end def self.args! if RUBY_PLATFORM !~ /darwin/ and ARGV.empty? Shoes.splash end OPTS.parse! ARGV ARGV[0] or true end def self.uri(str) if str =~ SHOES_URL_RE URI("http://#$1:53045#$2") else URI(str) rescue nil end end def self.visit(path) uri = Shoes.uri(path) case uri when URI::HTTP str = uri.read if str !~ /Shoes\.app/ Shoes.app do eval(uri.read) end else eval(uri.read) end else path = File.expand_path(path.gsub(/\\/, "/")) if path =~ /\.shy$/ @shy = true require 'shoes/shy' base = File.basename(path, ".shy") tmpdir = "%s/shoes-%s.%d" % [Dir.tmpdir, base, $$] shy = Shy.x(path, tmpdir) Dir.chdir(tmpdir) Shoes.debug "Loaded SHY: #{shy.name} #{shy.version} by #{shy.creator}" path = shy.launch else @shy = false Dir.chdir(File.dirname(path)) path = File.basename(path) end $0.replace path code = read_file(path) eval(code, TOPLEVEL_BINDING, path) end rescue SettingUp rescue Object => e error(e) show_log end def self.read_file path if RUBY_VERSION =~ /^1\.9/ and !@shy #File.open(path, 'r:utf-8') { |f| f.read } IO.read(path).force_encoding("UTF-8") else File.read(path) end end def self.url(path, meth) Shoes.mount(path, [self, meth]) end module Basic def tween opts, &blk opts = opts.dup if opts[:upward] opts[:top] = self.top - opts.delete(:upward) elsif opts[:downward] opts[:top] = self.top + opts.delete(:downward) end if opts[:sideways] opts[:left] = self.left + opts.delete(:sideways) end @TWEEN.remove if @TWEEN @TWEEN = parent.animate(opts[:speed] || 20) do # figure out a coordinate halfway between here and there cont = opts.select do |k, v| if self.respond_to? k n, o = v, self.send(k) if n != o n = o + ((n - o) / 2) n = v if o == n self.send("#{k}=", n) end self.style[k] != v end end # if we're there, get rid of the animation if cont.empty? @TWEEN.remove @TWEEN = nil blk.call if blk end end end end # complete list of styles BASIC_S = [:left, :top, :right, :bottom, :width, :height, :attach, :hidden, :displace_left, :displace_top, :margin, :margin_left, :margin_top, :margin_right, :margin_bottom] TEXT_S = [:strikecolor, :undercolor, :font, :size, :family, :weight, :rise, :kerning, :emphasis, :strikethrough, :stretch, :underline, :variant] MOUSE_S = [:click, :motion, :release, :hover, :leave] KEY_S = [:keydown, :keypress, :keyup] COLOR_S = [:stroke, :fill] {Background => [:angle, :radius, :curve, *BASIC_S], Border => [:angle, :radius, :curve, :strokewidth, *BASIC_S], Canvas => [:scroll, :start, :finish, *(KEY_S|MOUSE_S|BASIC_S)], Check => [:click, :checked, *BASIC_S], Radio => [:click, :checked, :group, *BASIC_S], EditLine => [:change, :secret, :text, *BASIC_S], EditBox => [:change, :text, *BASIC_S], Effect => [:radius, :distance, :inner, *(COLOR_S|BASIC_S)], Image => MOUSE_S|BASIC_S, ListBox => [:change, :items, :choose, *BASIC_S], # Pattern => [:angle, :radius, *BASIC_S], Progress => BASIC_S, Shape => COLOR_S|MOUSE_S|BASIC_S, TextBlock => [:justify, :align, :leading, *(COLOR_S|MOUSE_S|TEXT_S|BASIC_S)], Text => COLOR_S|MOUSE_S|TEXT_S|BASIC_S}. each do |klass, styles| klass.class_eval do include Basic styles.each do |m| case m when *MOUSE_S else define_method(m) { style[m] } unless klass.method_defined? m define_method("#{m}=") { |v| style(m => v) } unless klass.method_defined? "#{m}=" end end end end class Types::Widget @types = {} def self.inherited subc methc = subc.to_s[/(^|::)(\w+)$/, 2]. gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). gsub(/([a-z\d])([A-Z])/,'\1_\2').downcase @types[methc] = subc Shoes.class_eval %{ def #{methc}(*a, &b) a.unshift Widget.instance_variable_get("@types")[#{methc.dump}] widget(*a, &b) end } end end end def window(*a, &b) Shoes.app(*a, &b) end