under-os-ui 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +26 -0
  3. data/lib/assets/fontawesome-webfont.ttf +0 -0
  4. data/lib/assets/under-os.css +115 -0
  5. data/lib/core/kernel.rb +16 -0
  6. data/lib/under-os-ui.rb +6 -0
  7. data/lib/under_os/app.rb +26 -0
  8. data/lib/under_os/config.rb +25 -0
  9. data/lib/under_os/history.rb +53 -0
  10. data/lib/under_os/page.rb +178 -0
  11. data/lib/under_os/page/builder.rb +96 -0
  12. data/lib/under_os/page/layout.rb +43 -0
  13. data/lib/under_os/page/matcher.rb +128 -0
  14. data/lib/under_os/page/stylesheet.rb +67 -0
  15. data/lib/under_os/parser.rb +24 -0
  16. data/lib/under_os/parser/css.rb +37 -0
  17. data/lib/under_os/parser/html.rb +97 -0
  18. data/lib/under_os/ui.rb +3 -0
  19. data/lib/under_os/ui/alert.rb +52 -0
  20. data/lib/under_os/ui/button.rb +42 -0
  21. data/lib/under_os/ui/collection.rb +65 -0
  22. data/lib/under_os/ui/collection/cell.rb +21 -0
  23. data/lib/under_os/ui/collection/delegate.rb +70 -0
  24. data/lib/under_os/ui/collection/item.rb +32 -0
  25. data/lib/under_os/ui/collection/layout.rb +43 -0
  26. data/lib/under_os/ui/collection/styles.rb +15 -0
  27. data/lib/under_os/ui/div.rb +3 -0
  28. data/lib/under_os/ui/form.rb +60 -0
  29. data/lib/under_os/ui/icon.rb +61 -0
  30. data/lib/under_os/ui/icon/awesome.rb +376 -0
  31. data/lib/under_os/ui/icon/engine.rb +9 -0
  32. data/lib/under_os/ui/image.rb +31 -0
  33. data/lib/under_os/ui/input.rb +140 -0
  34. data/lib/under_os/ui/label.rb +21 -0
  35. data/lib/under_os/ui/locker.rb +42 -0
  36. data/lib/under_os/ui/navbar.rb +123 -0
  37. data/lib/under_os/ui/progress.rb +17 -0
  38. data/lib/under_os/ui/scroll.rb +102 -0
  39. data/lib/under_os/ui/select.rb +95 -0
  40. data/lib/under_os/ui/sidebar.rb +45 -0
  41. data/lib/under_os/ui/slider.rb +37 -0
  42. data/lib/under_os/ui/spinner.rb +23 -0
  43. data/lib/under_os/ui/style.rb +21 -0
  44. data/lib/under_os/ui/style/fonts.rb +56 -0
  45. data/lib/under_os/ui/style/margins.rb +164 -0
  46. data/lib/under_os/ui/style/outlining.rb +170 -0
  47. data/lib/under_os/ui/style/positioning.rb +183 -0
  48. data/lib/under_os/ui/switch.rb +26 -0
  49. data/lib/under_os/ui/textarea.rb +19 -0
  50. data/lib/under_os/ui/utils/animation.rb +101 -0
  51. data/lib/under_os/ui/utils/commons.rb +70 -0
  52. data/lib/under_os/ui/utils/dimensions.rb +37 -0
  53. data/lib/under_os/ui/utils/events.rb +210 -0
  54. data/lib/under_os/ui/utils/manipulation.rb +44 -0
  55. data/lib/under_os/ui/utils/position.rb +21 -0
  56. data/lib/under_os/ui/utils/size.rb +21 -0
  57. data/lib/under_os/ui/utils/styles.rb +89 -0
  58. data/lib/under_os/ui/utils/traversing.rb +44 -0
  59. data/lib/under_os/ui/utils/wrap.rb +77 -0
  60. data/lib/under_os/ui/view.rb +31 -0
  61. data/spec/assets/app.css +13 -0
  62. data/spec/assets/test.css +7 -0
  63. data/spec/assets/test.html +3 -0
  64. data/spec/assets/test.png +0 -0
  65. data/spec/assets/test_page.rb +2 -0
  66. data/spec/under_os/page/builder_spec.rb +128 -0
  67. data/spec/under_os/page/layout_spec.rb +18 -0
  68. data/spec/under_os/page/matcher_spec.rb +260 -0
  69. data/spec/under_os/page/stylesheet_spec.rb +83 -0
  70. data/spec/under_os/page_spec.rb +5 -0
  71. data/spec/under_os/parser/css_spec.rb +77 -0
  72. data/spec/under_os/parser/html_spec.rb +152 -0
  73. data/spec/under_os/parser_spec.rb +16 -0
  74. data/spec/under_os/ui/button_spec.rb +50 -0
  75. data/spec/under_os/ui/collection_spec.rb +19 -0
  76. data/spec/under_os/ui/div_spec.rb +24 -0
  77. data/spec/under_os/ui/form_spec.rb +156 -0
  78. data/spec/under_os/ui/icon_spec.rb +57 -0
  79. data/spec/under_os/ui/image_spec.rb +39 -0
  80. data/spec/under_os/ui/input_spec.rb +109 -0
  81. data/spec/under_os/ui/label_spec.rb +22 -0
  82. data/spec/under_os/ui/locker_spec.rb +31 -0
  83. data/spec/under_os/ui/progress_spec.rb +31 -0
  84. data/spec/under_os/ui/scroll_spec.rb +75 -0
  85. data/spec/under_os/ui/select_spec.rb +135 -0
  86. data/spec/under_os/ui/sidebar_spec.rb +35 -0
  87. data/spec/under_os/ui/slider_spec.rb +69 -0
  88. data/spec/under_os/ui/spinner_spec.rb +57 -0
  89. data/spec/under_os/ui/style/fonts_spec.rb +111 -0
  90. data/spec/under_os/ui/style/margins_spec.rb +106 -0
  91. data/spec/under_os/ui/style/outlining_spec.rb +101 -0
  92. data/spec/under_os/ui/style/positioning_spec.rb +69 -0
  93. data/spec/under_os/ui/style_spec.rb +19 -0
  94. data/spec/under_os/ui/switch_spec.rb +60 -0
  95. data/spec/under_os/ui/textarea_spec.rb +34 -0
  96. data/spec/under_os/ui/utils/commons_spec.rb +81 -0
  97. data/spec/under_os/ui/utils/events_spec.rb +87 -0
  98. data/spec/under_os/ui/utils/manipulation_spec.rb +130 -0
  99. data/spec/under_os/ui/utils/styles_spec.rb +140 -0
  100. data/spec/under_os/ui/utils/traversing_spec.rb +124 -0
  101. data/spec/under_os/ui/utils/wrap_spec.rb +69 -0
  102. data/spec/under_os/ui/view_spec.rb +39 -0
  103. data/under-os-ui.gemspec +23 -0
  104. metadata +216 -0
@@ -0,0 +1,96 @@
1
+ class UnderOs::Page::Builder
2
+
3
+ def self.views_from(source)
4
+ @inst ||= new
5
+ if source.is_a?(String)
6
+ @inst.from_html(source)
7
+ else
8
+ @inst.from_nodes(source)
9
+ end
10
+ end
11
+
12
+ def from_html(source)
13
+ @html ||= UnderOs::Parser::HTML.new
14
+ from_nodes @html.parse(source)
15
+ end
16
+
17
+ def from_nodes(list)
18
+ list.map do |node|
19
+ build_view_from(node)
20
+ end
21
+ end
22
+
23
+ def build_view_from(node)
24
+ klass = class_for(node)
25
+ options = options_from_attrs(node[:attrs] || {})
26
+
27
+ if klass.instance_methods.include?(:text) && node[:text]
28
+ options[:text] = node[:text]
29
+ end
30
+
31
+ klass.new(options).tap do |view|
32
+ if node[:children] && node[:children].any?
33
+ build_children_for(view, node[:children])
34
+ end
35
+ end
36
+ end
37
+
38
+ def class_for(node)
39
+ if node[:attrs] && node[:attrs][:wrapper].is_a?(String)
40
+ node[:attrs].delete(:wrapper).constantize
41
+ else
42
+ UnderOs::UI::Wrap::WRAPS_TAGS_MAP[node[:tag]] || UnderOs::UI::View
43
+ end
44
+ end
45
+
46
+ def options_from_attrs(hash)
47
+ {}.tap do |options|
48
+ hash.each do |key, value|
49
+ options[key] = value
50
+ end
51
+ end
52
+ end
53
+
54
+ def build_children_for(view, nodes)
55
+ if view.is_a?(UnderOs::UI::Select)
56
+ view.optgroups = select_optgroups_from(nodes)
57
+ elsif view.is_a?(UnderOs::UI::Collection)
58
+ find_collection_items_for(view, nodes)
59
+ else
60
+ from_nodes(nodes).each do |child|
61
+ view.insert child
62
+ end
63
+ end
64
+ end
65
+
66
+ def select_optgroups_from(nodes)
67
+ nodes = [{tag: 'optgroup', children: nodes}] if nodes[0][:tag] == 'option'
68
+
69
+ nodes.map do |group|
70
+ {}.tap do |options|
71
+ group[:children].each do |option|
72
+ if option[:tag] == 'option'
73
+ label = option[:text]
74
+ value = option[:attrs][:value] || label
75
+
76
+ options[value] = label if value && label
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ def find_collection_items_for(collection, nodes)
84
+ nodes.each do |node|
85
+ klass = class_for(node)
86
+
87
+ if klass <= UnderOs::UI::Collection::Item
88
+ klass.build(collection) { from_nodes(node[:children]) }
89
+ end
90
+
91
+ # header
92
+ # footer
93
+ end
94
+ end
95
+
96
+ end
@@ -0,0 +1,43 @@
1
+ class UnderOs::Page::Layout
2
+
3
+ def initialize(page)
4
+ @page = page
5
+
6
+ build_layout_from_xib || build_layout_from_html || build_layout_from_nothing
7
+
8
+ @page.view = UnderOs::UI::View.new(@page._.view)
9
+ @page.view.instance_variable_set('@_tag_name', 'PAGE')
10
+ end
11
+
12
+ def build_layout_from_xib
13
+ if filename = find_layout_with_type('xib')
14
+ @page._.view = NSBundle.mainBundle.loadNibNamed(filename.sub(/\.xib$/, ''), owner:self, options:nil)[0]
15
+ end
16
+ end
17
+
18
+ def build_layout_from_html
19
+ if filename = find_layout_with_type('html')
20
+ if layout = UnderOs::Parser.parse(filename)
21
+ if view = UnderOs::Page::Builder.views_from(layout)[0]
22
+ @page._.view = view._
23
+ @page.title = layout[0][:attrs][:title] || ''
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ def build_layout_from_nothing
30
+ @page._.view = UIView.alloc.init
31
+ @page._.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight
32
+ end
33
+
34
+ def find_layout_with_type(ext)
35
+ filename = @page.class.layout || "#{@page.name}.#{ext}"
36
+ filename, type = filename.match(/^(.+?)\.([a-z]+)$/).to_a.slice(1,2)
37
+
38
+ if type == ext && NSBundle.mainBundle.pathForResource(filename, ofType:type)
39
+ "#{filename}.#{type}"
40
+ end
41
+ end
42
+
43
+ end
@@ -0,0 +1,128 @@
1
+ class UnderOs::Page::StylesMatcher
2
+
3
+ def self.new(css_rule)
4
+ @cache ||= {}
5
+ @cache[css_rule] ||= super
6
+ end
7
+
8
+ def initialize(css_rule)
9
+ css_rules = css_rule.strip.split(/\s*,\s*/)
10
+
11
+ if css_rules.size == 1
12
+ rules = css_rules[0].scan(/([\S]+)/).map(&:first)
13
+ @rule = parse(rules.pop)
14
+ @parent = rules.size == 0 ? false : self.class.new(rules.join(' '))
15
+ else
16
+ @multiple_matchers = css_rules.map{ |rule| self.class.new(rule) }
17
+ end
18
+ end
19
+
20
+ def match(view)
21
+ score_for(view) != 0
22
+ end
23
+
24
+ def score_for(view)
25
+ return summary_score_for(view) if @multiple_matchers
26
+
27
+ id_score = id_score_for(view)
28
+ tag_score = tag_score_for(view)
29
+ class_score = class_score_for(view)
30
+ attrs_score = attrs_score_for(view)
31
+ total_score = id_score + tag_score + class_score + attrs_score
32
+
33
+ total_score = 0 if id_score == 0 && @rule[:id]
34
+ total_score = 0 if tag_score == 0 && @rule[:tag]
35
+ total_score = 0 if class_score == 0 && @rule[:classes].size != 0
36
+ total_score = 0 if attrs_score == 0 && @rule[:attrs].size != 0
37
+
38
+ if @parent && total_score != 0
39
+ parent_score = parent_score_for(view)
40
+ total_score = parent_score == 0 ? 0 : total_score + parent_score
41
+ end
42
+
43
+ total_score
44
+ end
45
+
46
+ private
47
+
48
+ def summary_score_for(view)
49
+ @multiple_matchers.map{ |matcher| matcher.score_for(view) }.max
50
+ end
51
+
52
+ def id_score_for(view)
53
+ @rule[:id] == view.id ? 1 : 0
54
+ end
55
+
56
+ def tag_score_for(view)
57
+ @rule[:tag] == '*' || @rule[:tag] == view.tagName ? 1 : 0
58
+ end
59
+
60
+ def class_score_for(view)
61
+ return 0 if @rule[:classes].size == 0
62
+ match = @rule[:classes] & view.classNames
63
+ match.size == @rule[:classes].size ? match.size : 0
64
+ end
65
+
66
+ def attrs_score_for(view)
67
+ score = 0; return 0 if @rule[:attrs].size == 0
68
+
69
+ @rule[:attrs].each do |key, value, operand|
70
+ attr = view.respond_to?(key) && view.__send__(key)
71
+ attr = attr.to_s if attr.is_a?(Symbol) || attr.is_a?(Numeric)
72
+
73
+ if attr && attr.is_a?(String)
74
+ matches = case operand
75
+ when '=' then attr == value
76
+ when '*=' then attr.include?(value)
77
+ when '^=' then attr.starts_with?(value)
78
+ when '$=' then attr.ends_with?(value)
79
+ when '~=' then attr.split(' ').include?(value)
80
+ when '|=' then attr.split('-').include?(value)
81
+ end
82
+
83
+ score += 1 if matches
84
+ end
85
+ end
86
+
87
+ score
88
+ end
89
+
90
+ def parent_score_for(view)
91
+ parent = view; score = 0
92
+
93
+ while parent = parent.parent
94
+ score = @parent.score_for(parent)
95
+ break if score > 0
96
+ end
97
+
98
+ score
99
+ end
100
+
101
+ def parse(string)
102
+ {}.tap do |rule|
103
+ if m = string.match(/^([a-z\*]+)/)
104
+ rule[:tag] = m[1].upcase
105
+ else
106
+ rule[:tag] = false # so it wouldn't match nil
107
+ end
108
+
109
+ if m = string.match(/#([a-z_\-0-9]+)/)
110
+ rule[:id] = m[1]
111
+ else
112
+ rule[:id] = false # so it wouldn't match nil
113
+ end
114
+
115
+ if m = string.scan(/(\.)([a-z_\-0-9]+)/)
116
+ rule[:classes] = m.map{|c| c[1]}
117
+ else
118
+ rule[:classes] = []
119
+ end
120
+
121
+ if m = string.scan(/\[\s*([a-z]+)\s*([*^$~|]{0,1}=)\s*('|")?(.+?)(\3)?\s*\]/)
122
+ rule[:attrs] = m.map{|c| [c[0], c[3], c[1]] }
123
+ else
124
+ rule[:attrs] = []
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,67 @@
1
+ class UnderOs::Page::Stylesheet
2
+ attr_reader :rules
3
+
4
+ def initialize(styles={})
5
+ @rules = {}
6
+
7
+ styles.each do |rule, style|
8
+ self[rule] = style
9
+ end
10
+ end
11
+
12
+ def styles_for(view)
13
+ {}.tap do |styles|
14
+ weighted_styles_for(view).each do |hash|
15
+ hash.each do |key, value|
16
+ styles[key] = value
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ def [](rule)
23
+ @rules[rule.to_s]
24
+ end
25
+
26
+ def []=(rule, values)
27
+ @rules[rule.to_s] ||= {}
28
+ @rules[rule.to_s].merge! values
29
+ end
30
+
31
+ def <<(another_stylesheet)
32
+ another_stylesheet.rules.each do |rule, styles|
33
+ self[rule] = styles
34
+ end
35
+ end
36
+
37
+ def +(another_stylesheet)
38
+ self.class.new.tap do |combined_sheet|
39
+ combined_sheet << self
40
+ combined_sheet << another_stylesheet
41
+ end
42
+ end
43
+
44
+ def load(filename)
45
+ UnderOs::Parser.parse(filename).each do |rule, styles|
46
+ self[rule] = styles
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def weighted_styles_for(view)
53
+ find_styles_for(view).
54
+ sort{|a,b| a[:score] <=> b[:score]}.
55
+ map{|e| e[:style]}
56
+ end
57
+
58
+ def find_styles_for(view)
59
+ [].tap do |styles|
60
+ @rules.each do |css, rule|
61
+ score = UnderOs::Page::StylesMatcher.new(css).score_for(view)
62
+ styles << {score: score, style: rule} if score != 0
63
+ end
64
+ end
65
+ end
66
+
67
+ end
@@ -0,0 +1,24 @@
1
+ #
2
+ # Generic templates/stylesheets parsing engine
3
+ #
4
+ class UnderOs::Parser
5
+ def self.parse(*args)
6
+ @inst ||= new
7
+ @inst.parse *args
8
+ end
9
+
10
+ def initialize
11
+ @css = CSS.new
12
+ @html = HTML.new
13
+ end
14
+
15
+ def parse(filename)
16
+ filepath = NSBundle.mainBundle.pathForResource(filename, ofType:nil)
17
+ content = filepath ? UnderOs::File.read(filepath) : ''
18
+
19
+ case filename.split('.').pop
20
+ when 'css' then @css.parse(content)
21
+ when 'html' then @html.parse(content)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,37 @@
1
+ class UnderOs::Parser::CSS
2
+ def parse(style)
3
+ style = style.gsub(/\/\*[\s\S]+?\*\//, '').strip
4
+
5
+ {}.tap do |result|
6
+ style.scan(/(\A|\})([a-z0-9_\*\-\.\s#:,]+)\{([^}]+)/).map do |rule|
7
+ values = parse_styles(rule[2])
8
+
9
+ rule[1].split(',').each do |css_rule|
10
+ result[css_rule.gsub(/\s+/, ' ').strip] = values
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ def parse_styles(styles)
17
+ {}.tap do |hash|
18
+ styles.scan(/([a-z\-]+)\s*:\s*([^;]+)\s*/).each do |param|
19
+ hash.merge! normalized_values(param[0], param[1])
20
+ end
21
+ end
22
+ end
23
+
24
+ def normalized_values(key, value)
25
+ key = key.camelize.to_sym
26
+ value = value.strip.gsub(/px$/, '')
27
+ value = value.gsub(/^('|")(.*?)\1$/, '\2')
28
+ value = value.to_f if value =~ /^[\-\d\.]+$/
29
+
30
+ if key == :background && value =~ /^[\S]+$/
31
+ key = :backgroundColor
32
+ end
33
+
34
+
35
+ {key => value}
36
+ end
37
+ end
@@ -0,0 +1,97 @@
1
+ class UnderOs::Parser::HTML
2
+ def parse(html)
3
+ html = html.strip.gsub(/<\!--[\s\S]*?-->/, '').gsub(/>\s+/, '>').gsub(/\s+</, '<')
4
+
5
+ [].tap do |top|
6
+ @top = top
7
+ @stack = []
8
+ @node = nil
9
+ i = 0
10
+
11
+ while i < html.size
12
+ @chunk = html.slice(i, html.size)
13
+
14
+ i += open_tag || close_tag || plain_text
15
+ end
16
+
17
+ # closing all the missing tags
18
+ while node = @stack.shift
19
+ node.delete(:children)
20
+ node.delete(:text)
21
+ @top << node if ! @top.include?(node)
22
+ end
23
+ end
24
+ end
25
+
26
+ def open_tag
27
+ if m = @chunk.match(/\A<([a-z]+)([^>]*)>/)
28
+ @node = {tag: m[1], attrs: parse_attrs_in(m[2])}
29
+
30
+ if parent = @stack.last
31
+ parent[:children] ||= []
32
+ parent[:children] << @node
33
+ parent.delete(:text) # it can have either text or children
34
+ else
35
+ @top << @node
36
+ end
37
+
38
+ @stack << @node
39
+
40
+ m[0].size
41
+ end
42
+ end
43
+
44
+ def close_tag
45
+ if m = @chunk.match(/\A<\/([a-z]+)>/)
46
+ while node = @stack.pop
47
+ if node[:tag] != m[1]
48
+ if @stack.size > 0
49
+ @stack.last[:children] += node[:children] || []
50
+ node.delete(:children)
51
+ node.delete(:text)
52
+ end
53
+ else
54
+ break
55
+ end
56
+ end
57
+
58
+ @node = @stack.last
59
+
60
+ m[0].size
61
+ end
62
+ end
63
+
64
+ def plain_text
65
+ if m = @chunk.match(/\A([^<]+)/)
66
+ @stack.last[:text] = m[1] if @stack.last
67
+
68
+ m[0].size
69
+ end
70
+ end
71
+
72
+ def parse_attrs_in(string)
73
+ merge_data_attrs({}.tap do |hash|
74
+ string.scan(/([a-z][a-z_\-\d]+)=('|")(.+?)(\2)/).each do |match|
75
+ value = match[0] == match[2] ? true : match[2]
76
+ value = true if value == 'true'
77
+ value = false if value == 'false'
78
+ hash[match[0].to_sym] = value
79
+ end
80
+ end)
81
+ end
82
+
83
+ def merge_data_attrs(hash)
84
+ hash.keys.each do |key|
85
+ if key.to_s.starts_with?('data-')
86
+ hash[:data] ||= {}
87
+
88
+ value = hash.delete(key)
89
+ key = key.to_s.gsub(/^data\-/, '').camelize
90
+
91
+ hash[:data][key.to_sym] = value
92
+ end
93
+ end
94
+
95
+ hash
96
+ end
97
+ end