under-os-ui 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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