under-os 0.0.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +16 -0
- data/README.md +70 -0
- data/Rakefile +24 -0
- data/app/config.rb +4 -0
- data/app/layouts/alerts.html +8 -0
- data/app/layouts/calculator.html +26 -0
- data/app/layouts/collections.html +7 -0
- data/app/layouts/home.html +14 -0
- data/app/layouts/http.html +8 -0
- data/app/layouts/inputs.html +44 -0
- data/app/layouts/lockers.html +6 -0
- data/app/layouts/navbar.html +9 -0
- data/app/layouts/scrolls.html +42 -0
- data/app/layouts/sidebars.html +17 -0
- data/app/pages/alerts_page.rb +14 -0
- data/app/pages/calculator_page.rb +36 -0
- data/app/pages/collections_page.rb +9 -0
- data/app/pages/home_page.rb +11 -0
- data/app/pages/http_page.rb +33 -0
- data/app/pages/inputs_page.rb +9 -0
- data/app/pages/lockers_page.rb +16 -0
- data/app/pages/navbar_page.rb +19 -0
- data/app/pages/scrolls_page.rb +5 -0
- data/app/pages/sidebars_page.rb +42 -0
- data/app/pages/stuff_page.rb +35 -0
- data/app/styles/application.css +25 -0
- data/app/styles/calculator.css +37 -0
- data/app/styles/collections.css +19 -0
- data/app/styles/home.css +0 -0
- data/app/styles/http.css +24 -0
- data/app/styles/inputs.css +47 -0
- data/app/styles/scrolls.css +67 -0
- data/app/styles/sidebars.css +19 -0
- data/lib/assets/fontawesome-webfont.ttf +0 -0
- data/lib/assets/under-os.css +115 -0
- data/lib/under-os.rb +9 -1
- data/lib/under_os/app.rb +33 -0
- data/lib/under_os/color.rb +176 -0
- data/lib/under_os/config.rb +28 -1
- data/lib/under_os/core/kernel.rb +16 -0
- data/lib/under_os/core/numeric.rb +23 -0
- data/lib/under_os/core/string.rb +40 -0
- data/lib/under_os/delegate.rb +3 -2
- data/lib/under_os/events.rb +85 -0
- data/lib/under_os/file.rb +110 -0
- data/lib/under_os/history.rb +50 -0
- data/lib/under_os/http/receiver.rb +44 -0
- data/lib/under_os/http/request.rb +89 -0
- data/lib/under_os/http/response.rb +24 -0
- data/lib/under_os/http/session.rb +49 -0
- data/lib/under_os/http.rb +5 -0
- data/lib/under_os/page/builder.rb +96 -0
- data/lib/under_os/page/layout.rb +43 -0
- data/lib/under_os/page/matcher.rb +83 -0
- data/lib/under_os/page/stylesheet.rb +67 -0
- data/lib/under_os/page.rb +153 -0
- data/lib/under_os/parser/css.rb +37 -0
- data/lib/under_os/parser/html.rb +97 -0
- data/lib/under_os/parser.rb +24 -0
- data/lib/under_os/point.rb +47 -0
- data/lib/under_os/screen.rb +9 -0
- data/lib/under_os/timer.rb +65 -0
- data/lib/under_os/ui/alert.rb +52 -0
- data/lib/under_os/ui/button.rb +22 -0
- data/lib/under_os/ui/collection/cell.rb +21 -0
- data/lib/under_os/ui/collection/delegate.rb +68 -0
- data/lib/under_os/ui/collection/item.rb +32 -0
- data/lib/under_os/ui/collection/layout.rb +43 -0
- data/lib/under_os/ui/collection/styles.rb +15 -0
- data/lib/under_os/ui/collection.rb +63 -0
- data/lib/under_os/ui/icon/awesome.rb +376 -0
- data/lib/under_os/ui/icon/engine.rb +10 -0
- data/lib/under_os/ui/icon.rb +42 -0
- data/lib/under_os/ui/image.rb +30 -0
- data/lib/under_os/ui/input.rb +41 -0
- data/lib/under_os/ui/label.rb +21 -0
- data/lib/under_os/ui/locker.rb +42 -0
- data/lib/under_os/ui/navbar.rb +115 -0
- data/lib/under_os/ui/progress.rb +17 -0
- data/lib/under_os/ui/scroll.rb +41 -0
- data/lib/under_os/ui/select.rb +98 -0
- data/lib/under_os/ui/sidebar.rb +45 -0
- data/lib/under_os/ui/slider.rb +49 -0
- data/lib/under_os/ui/spinner.rb +23 -0
- data/lib/under_os/ui/style/fonts.rb +60 -0
- data/lib/under_os/ui/style/margins.rb +160 -0
- data/lib/under_os/ui/style/outlining.rb +98 -0
- data/lib/under_os/ui/style/positioning.rb +169 -0
- data/lib/under_os/ui/style.rb +21 -0
- data/lib/under_os/ui/switch.rb +29 -0
- data/lib/under_os/ui/textarea.rb +14 -0
- data/lib/under_os/ui/utils/animation.rb +99 -0
- data/lib/under_os/ui/utils/commons.rb +70 -0
- data/lib/under_os/ui/utils/dimensions.rb +37 -0
- data/lib/under_os/ui/utils/editable.rb +43 -0
- data/lib/under_os/ui/utils/events.rb +75 -0
- data/lib/under_os/ui/utils/manipulation.rb +44 -0
- data/lib/under_os/ui/utils/position.rb +21 -0
- data/lib/under_os/ui/utils/size.rb +21 -0
- data/lib/under_os/ui/utils/styles.rb +89 -0
- data/lib/under_os/ui/utils/traversing.rb +44 -0
- data/lib/under_os/ui/utils/wrap.rb +77 -0
- data/lib/under_os/ui/view.rb +31 -0
- data/lib/under_os/ui.rb +3 -0
- data/lib/under_os.rb +2 -2
- data/resources/Default-568h@2x.png +0 -0
- data/resources/test.png +0 -0
- data/spec/assets/app.css +13 -0
- data/spec/assets/test.css +7 -0
- data/spec/assets/test.html +3 -0
- data/spec/assets/test_page.rb +2 -0
- data/spec/lib/core/numeric_spec.rb +37 -0
- data/spec/lib/core/string_spec.rb +87 -0
- data/spec/lib/under_os/color_spec.rb +133 -0
- data/spec/lib/under_os/events_spec.rb +71 -0
- data/spec/lib/under_os/file_spec.rb +98 -0
- data/spec/lib/under_os/page/builder_spec.rb +128 -0
- data/spec/lib/under_os/page/layout_spec.rb +18 -0
- data/spec/lib/under_os/page/matcher_spec.rb +154 -0
- data/spec/lib/under_os/page/stylesheet_spec.rb +83 -0
- data/spec/lib/under_os/page_spec.rb +5 -0
- data/spec/lib/under_os/parser/css_spec.rb +77 -0
- data/spec/lib/under_os/parser/html_spec.rb +152 -0
- data/spec/lib/under_os/parser_spec.rb +16 -0
- data/spec/lib/under_os/point_spec.rb +54 -0
- data/spec/lib/under_os/screen_spec.rb +11 -0
- data/spec/lib/under_os/timer_spec.rb +93 -0
- data/spec/lib/under_os/ui/button_spec.rb +5 -0
- data/spec/lib/under_os/ui/collection_spec.rb +19 -0
- data/spec/lib/under_os/ui/icon_spec.rb +26 -0
- data/spec/lib/under_os/ui/image_spec.rb +39 -0
- data/spec/lib/under_os/ui/input_spec.rb +77 -0
- data/spec/lib/under_os/ui/label_spec.rb +22 -0
- data/spec/lib/under_os/ui/locker_spec.rb +31 -0
- data/spec/lib/under_os/ui/progress_spec.rb +31 -0
- data/spec/lib/under_os/ui/scroll_spec.rb +40 -0
- data/spec/lib/under_os/ui/select_spec.rb +131 -0
- data/spec/lib/under_os/ui/sidebar_spec.rb +35 -0
- data/spec/lib/under_os/ui/slider_spec.rb +65 -0
- data/spec/lib/under_os/ui/spinner_spec.rb +57 -0
- data/spec/lib/under_os/ui/style/fonts_spec.rb +73 -0
- data/spec/lib/under_os/ui/style/margins_spec.rb +106 -0
- data/spec/lib/under_os/ui/style/outlining_spec.rb +69 -0
- data/spec/lib/under_os/ui/style/positioning_spec.rb +69 -0
- data/spec/lib/under_os/ui/style_spec.rb +19 -0
- data/spec/lib/under_os/ui/switch_spec.rb +56 -0
- data/spec/lib/under_os/ui/textarea_spec.rb +30 -0
- data/spec/lib/under_os/ui/utils/commons_spec.rb +81 -0
- data/spec/lib/under_os/ui/utils/manipulation_spec.rb +130 -0
- data/spec/lib/under_os/ui/utils/styles_spec.rb +140 -0
- data/spec/lib/under_os/ui/utils/traversing_spec.rb +124 -0
- data/spec/lib/under_os/ui/utils/wrap_spec.rb +69 -0
- data/spec/lib/under_os/ui/view_spec.rb +39 -0
- data/under-os.gemspec +32 -0
- metadata +225 -11
- data/lib/under_os/application.rb +0 -16
- data/lib/under_os/window.rb +0 -5
@@ -0,0 +1,153 @@
|
|
1
|
+
#
|
2
|
+
# UOS::Page is kind of a wrap over an iOS controller
|
3
|
+
# but instead of the controller, it presents more the
|
4
|
+
# main (root) view of the controller in a sence you've
|
5
|
+
# got for DOM documents in web
|
6
|
+
#
|
7
|
+
class UnderOs::Page
|
8
|
+
include UnderOs::Events
|
9
|
+
include UnderOs::UI
|
10
|
+
|
11
|
+
attr_reader :_, :stylesheet
|
12
|
+
|
13
|
+
def self.new(*args)
|
14
|
+
alloc.setup_wrap(*args)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.layout(name=nil)
|
18
|
+
name ? (@layout = name) : @layout
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
# page building goes in here
|
23
|
+
end
|
24
|
+
|
25
|
+
def view
|
26
|
+
@_view
|
27
|
+
end
|
28
|
+
|
29
|
+
def view=(view)
|
30
|
+
@_view = view
|
31
|
+
end
|
32
|
+
|
33
|
+
%w[insert append prepend find first].each do |method|
|
34
|
+
define_method method do |*args|
|
35
|
+
@_view.__send__ method, *args
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def alert(*args)
|
40
|
+
Alert.new(*args)
|
41
|
+
end
|
42
|
+
|
43
|
+
def history
|
44
|
+
UnderOs::App.history
|
45
|
+
end
|
46
|
+
|
47
|
+
def navbar
|
48
|
+
history.navbar
|
49
|
+
end
|
50
|
+
|
51
|
+
def title
|
52
|
+
@_.navigationItem.title
|
53
|
+
end
|
54
|
+
|
55
|
+
def title=(text)
|
56
|
+
@_.navigationItem.title = text
|
57
|
+
end
|
58
|
+
|
59
|
+
def name
|
60
|
+
self.class.name.underscore.sub(/_page$/, '')
|
61
|
+
end
|
62
|
+
|
63
|
+
def setup_wrap(*args)
|
64
|
+
@_ = UIViewControllerWrap.alloc.init(self, {
|
65
|
+
on_load_view: Proc.new{ emit('init') },
|
66
|
+
on_view_loaded: Proc.new{ emit('load') },
|
67
|
+
on_view_appear: Proc.new{ emit('appear') },
|
68
|
+
on_view_disappear: Proc.new{ emit('disappear') },
|
69
|
+
on_view_rerender: Proc.new{ emit('rerender') },
|
70
|
+
on_view_rotate: Proc.new{ emit('rotate') }
|
71
|
+
})
|
72
|
+
|
73
|
+
on 'init' do
|
74
|
+
build_layout
|
75
|
+
compile_styles
|
76
|
+
end
|
77
|
+
|
78
|
+
on 'load' do
|
79
|
+
repaint
|
80
|
+
initialize(*args)
|
81
|
+
repaint
|
82
|
+
end
|
83
|
+
|
84
|
+
on 'rotate' do
|
85
|
+
repaint
|
86
|
+
end
|
87
|
+
|
88
|
+
self
|
89
|
+
end
|
90
|
+
|
91
|
+
def build_layout
|
92
|
+
@_layout = Layout.new(self)
|
93
|
+
end
|
94
|
+
|
95
|
+
def compile_styles
|
96
|
+
@stylesheet = Stylesheet.new
|
97
|
+
@stylesheet << UnderOs::App.stylesheet
|
98
|
+
@stylesheet.load("#{name}.css")
|
99
|
+
end
|
100
|
+
|
101
|
+
def repaint
|
102
|
+
view.repaint(stylesheet) if view
|
103
|
+
navbar.repaint(stylesheet) if navbar
|
104
|
+
end
|
105
|
+
|
106
|
+
#
|
107
|
+
# A simple wrap over UIViewController to intercept
|
108
|
+
# the iOS events
|
109
|
+
#
|
110
|
+
class UIViewControllerWrap < UIViewController
|
111
|
+
attr_reader :wrapper
|
112
|
+
|
113
|
+
def init(wrapper, options)
|
114
|
+
@wrapper = wrapper
|
115
|
+
@options = options
|
116
|
+
initWithNibName(nil, bundle: nil)
|
117
|
+
end
|
118
|
+
|
119
|
+
def loadView
|
120
|
+
super
|
121
|
+
@options[:on_load_view].call
|
122
|
+
end
|
123
|
+
|
124
|
+
def viewDidLoad
|
125
|
+
super
|
126
|
+
@options[:on_view_loaded].call
|
127
|
+
end
|
128
|
+
|
129
|
+
def viewDidAppear(animated)
|
130
|
+
super
|
131
|
+
@options[:on_view_appear].call
|
132
|
+
end
|
133
|
+
|
134
|
+
def viewDidDisappear(animated)
|
135
|
+
super
|
136
|
+
@options[:on_view_disappear].call
|
137
|
+
end
|
138
|
+
|
139
|
+
def viewWillLayoutSubviews
|
140
|
+
super
|
141
|
+
@options[:on_view_rerender].call
|
142
|
+
end
|
143
|
+
|
144
|
+
def didRotateFromInterfaceOrientation(orientation)
|
145
|
+
super
|
146
|
+
@options[:on_view_rotate].call
|
147
|
+
end
|
148
|
+
|
149
|
+
def prefersStatusBarHidden
|
150
|
+
!UnderOs::App.config.status_bar
|
151
|
+
end
|
152
|
+
end
|
153
|
+
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
|
@@ -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,47 @@
|
|
1
|
+
#
|
2
|
+
# Generic Point/Size unit
|
3
|
+
#
|
4
|
+
class UnderOs::Point
|
5
|
+
|
6
|
+
def initialize(x, y=nil)
|
7
|
+
if x.is_a?(UnderOs::Point)
|
8
|
+
y = x.y if x.y
|
9
|
+
x = x.x
|
10
|
+
elsif x.is_a?(Hash)
|
11
|
+
y = x[:y] || x['y'] || nil
|
12
|
+
x = x[:x] || x['x'] || nil
|
13
|
+
end
|
14
|
+
|
15
|
+
@x = x
|
16
|
+
@y = y
|
17
|
+
end
|
18
|
+
|
19
|
+
def x
|
20
|
+
@x
|
21
|
+
end
|
22
|
+
|
23
|
+
def y
|
24
|
+
@y
|
25
|
+
end
|
26
|
+
|
27
|
+
def ==(*args)
|
28
|
+
point = UnderOs::Point.new(*args) # normalizing
|
29
|
+
x == point.x && y == point.y
|
30
|
+
end
|
31
|
+
|
32
|
+
def *(multiplier)
|
33
|
+
self.class.new(x: @x * multiplier, y: @y * multiplier)
|
34
|
+
end
|
35
|
+
|
36
|
+
def /(divider)
|
37
|
+
self.class.new(x: @x / divider.to_f, y: @y / divider.to_f)
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_s
|
41
|
+
"x=#{x} y=#{y}"
|
42
|
+
end
|
43
|
+
|
44
|
+
def inspect
|
45
|
+
"#<#{self.class.name}:0x#{__id__.to_s(16)} #{to_s}>"
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
class UnderOs::Timer
|
2
|
+
def self.in(duration, options={}, &block)
|
3
|
+
duration = Duration.new(duration) if duration.is_a?(Numeric)
|
4
|
+
new duration.to_f, options.merge(repeat: false), &block
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.every(duration, options={}, &block)
|
8
|
+
duration = Duration.new(duration) if duration.is_a?(Numeric)
|
9
|
+
new duration.to_f, options.merge(repeat: true), &block
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :block, :counter, :interval, :repeats, :_
|
13
|
+
|
14
|
+
def initialize(seconds, options={}, &block)
|
15
|
+
@block = block
|
16
|
+
@counter = options[:repeat].to_i if options[:repeat].is_a?(Numeric)
|
17
|
+
@interval = seconds
|
18
|
+
@repeats = options[:repeat] != false
|
19
|
+
|
20
|
+
@_ = NSTimer.scheduledTimerWithTimeInterval @interval,
|
21
|
+
target: self, selector: :kick, userInfo: nil, repeats: @repeats
|
22
|
+
end
|
23
|
+
|
24
|
+
def stop
|
25
|
+
@_.invalidate
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def kick
|
30
|
+
@block.call
|
31
|
+
stop if @counter && (@counter -= 1) <= 0
|
32
|
+
end
|
33
|
+
|
34
|
+
class Duration
|
35
|
+
attr_reader :seconds
|
36
|
+
|
37
|
+
def initialize(seconds)
|
38
|
+
@seconds = seconds.to_f
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_f
|
42
|
+
@seconds
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_i
|
46
|
+
to_f.to_i
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_s
|
50
|
+
"#{to_f} seconds"
|
51
|
+
end
|
52
|
+
|
53
|
+
def ==(duration)
|
54
|
+
duration.is_a?(UnderOs::Timer::Duration) && duration.seconds == @seconds
|
55
|
+
end
|
56
|
+
|
57
|
+
def later(options={}, &block)
|
58
|
+
UnderOs::Timer.in self, options={}, &block
|
59
|
+
end
|
60
|
+
|
61
|
+
def repeat(options={}, &block)
|
62
|
+
UnderOs::Timer.every self, options={}, &block
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
class UnderOs::UI::Alert
|
2
|
+
include UnderOs::Events
|
3
|
+
|
4
|
+
def initialize(options={})
|
5
|
+
options = {message: options} if options.is_a?(String)
|
6
|
+
self.on = options.delete[:on] if options[:on]
|
7
|
+
|
8
|
+
@_ = CustomAlertView.alloc.initiWithOptions(self, options)
|
9
|
+
@_.show unless options[:show] == false
|
10
|
+
end
|
11
|
+
|
12
|
+
def show
|
13
|
+
@_.show
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def hide(animated=true)
|
18
|
+
@_.dismissWithClickedButtonIndex(0, animated: animated)
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def visible
|
23
|
+
@_.visible
|
24
|
+
end
|
25
|
+
|
26
|
+
def hidden
|
27
|
+
!visible
|
28
|
+
end
|
29
|
+
|
30
|
+
class CustomAlertView < UIAlertView
|
31
|
+
def initiWithOptions(wrapper, options)
|
32
|
+
@wrapper = wrapper
|
33
|
+
|
34
|
+
initWithTitle( options[:title] || "",
|
35
|
+
message: options[:message] || "No message given",
|
36
|
+
delegate: self,
|
37
|
+
cancelButtonTitle: options[:button] || "Ok",
|
38
|
+
otherButtonTitles: nil )
|
39
|
+
|
40
|
+
(options[:buttons] || []).each do |title|
|
41
|
+
addButtonWithTitle title
|
42
|
+
end
|
43
|
+
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
def alertView(alertView, willDismissWithButtonIndex:buttonIndex)
|
48
|
+
@wrapper.emit(:tap, buttonIndex: buttonIndex, buttonTitle: buttonTitleAtIndex(buttonIndex))
|
49
|
+
@wrapper.emit(:close)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class UnderOS::UI::Button < UnderOS::UI::View
|
2
|
+
wraps UIButton, tag: 'button'
|
3
|
+
|
4
|
+
def initialize(options={})
|
5
|
+
super
|
6
|
+
|
7
|
+
self.text = options.delete(:text) || ''
|
8
|
+
@_.showsTouchWhenHighlighted = true
|
9
|
+
@_.setBackgroundImage(options.delete(:image), forState:UIControlStateNormal) if options[:image]
|
10
|
+
@_.sizeToFit
|
11
|
+
end
|
12
|
+
|
13
|
+
def text
|
14
|
+
@_.currentTitle
|
15
|
+
end
|
16
|
+
|
17
|
+
def text=(new_text, state=UIControlStateNormal)
|
18
|
+
@_.setTitle new_text, forState:state
|
19
|
+
repaint
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
#
|
2
|
+
# This is a bridge to the iOS native functionality
|
3
|
+
# please do not use it in your code
|
4
|
+
#
|
5
|
+
class UnderOs::UI::Collection::Cell < UICollectionViewCell
|
6
|
+
|
7
|
+
def self.classes
|
8
|
+
@classes ||= {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def uos_view_for(collection)
|
12
|
+
@uos_view ||= self.class.classes[collection].for(collection).tap do |view|
|
13
|
+
contentView.addSubview(view._)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def prepareForReuse
|
18
|
+
super
|
19
|
+
@uos_view.cleanup if @uos_view
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
#
|
2
|
+
# The iOS level events receiver, not for a public use
|
3
|
+
#
|
4
|
+
class UnderOs::UI::Collection::Delegate < UIViewController
|
5
|
+
|
6
|
+
def self.new(collection)
|
7
|
+
alloc.tap do |instance|
|
8
|
+
instance.instance_eval do
|
9
|
+
@collection = collection
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
################################################################
|
15
|
+
# DataSource API
|
16
|
+
################################################################
|
17
|
+
|
18
|
+
def numberOfSectionsInCollectionView(collection)
|
19
|
+
@collection.number_of_sections
|
20
|
+
end
|
21
|
+
|
22
|
+
def collectionView(collection, numberOfItemsInSection: section)
|
23
|
+
@collection.number_of_items(section)
|
24
|
+
end
|
25
|
+
|
26
|
+
def collectionView(collection, cellForItemAtIndexPath: indexPath)
|
27
|
+
collection.dequeueReusableCellWithReuseIdentifier('UOSCollectionCell', forIndexPath:indexPath).tap do |item|
|
28
|
+
@collection.emit(:item, item: item.uos_view_for(@collection), index: indexPath.row, section: indexPath.section)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# def collectionView(collection, viewForSupplementaryElementOfKind: kind, atIndexPath: indexPath)
|
33
|
+
# # kind is a string
|
34
|
+
# UICollectionReusableView.alloc.init
|
35
|
+
# UICollectionReusableView * reusableview = nil ;
|
36
|
+
|
37
|
+
# if ( kind == UICollectionElementKindSectionHeader ) {
|
38
|
+
# RecipeCollectionHeaderView * headerView = [ collectionView dequeueReusableSupplementaryViewOfKind : UICollectionElementKindSectionHeader withReuseIdentifier : @ "HeaderView" forIndexPath : indexPath ] ;
|
39
|
+
# NSString * title = [ [ NSString alloc ] initWithFormat : @ "Recipe Group #%i" , indexPath.section + 1 ] ;
|
40
|
+
# headerView.title.text = title;
|
41
|
+
# UIImage * headerImage = [ UIImage imageNamed : @ "header_banner.png" ] ;
|
42
|
+
# headerView.backgroundImage.image = headerImage;
|
43
|
+
|
44
|
+
# reusableview = headerView;
|
45
|
+
# }
|
46
|
+
|
47
|
+
# if ( kind == UICollectionElementKindSectionFooter ) {
|
48
|
+
# UICollectionReusableView * footerview = [ collectionView dequeueReusableSupplementaryViewOfKind : UICollectionElementKindSectionFooter withReuseIdentifier : @ "FooterView" forIndexPath : indexPath ] ;
|
49
|
+
|
50
|
+
# reusableview = footerview;
|
51
|
+
# }
|
52
|
+
|
53
|
+
# return reusableview;
|
54
|
+
# end
|
55
|
+
|
56
|
+
|
57
|
+
################################################################
|
58
|
+
# DataSource API
|
59
|
+
################################################################
|
60
|
+
|
61
|
+
def collectionView(collection, didSelectItemAtIndexPath: indexPath)
|
62
|
+
@collection.emit(:select, index: indexPath.row, section: indexPath.section)
|
63
|
+
end
|
64
|
+
|
65
|
+
def collectionView(collection, didDeselectItemAtIndexPath: indexPath)
|
66
|
+
@collection.emit(:unselect, index: indexPath.row, section: indexPath.section)
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
#
|
2
|
+
# This is the collection item class that you supposed
|
3
|
+
# to inherit (well, if you need to)
|
4
|
+
#
|
5
|
+
class UnderOs::UI::Collection::Item < UnderOs::UI::View
|
6
|
+
|
7
|
+
tag :item
|
8
|
+
|
9
|
+
def self.build(collection, &builder)
|
10
|
+
@builders ||= {}
|
11
|
+
@builders[collection] = builder
|
12
|
+
collection.item_class = self
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.for(collection, stylesheet=nil)
|
16
|
+
new.tap do |view|
|
17
|
+
def view.parent; @_parent; end
|
18
|
+
view.instance_variable_set('@_parent', collection)
|
19
|
+
|
20
|
+
@builders[collection] && @builders[collection].call.each do |child|
|
21
|
+
view.insert child
|
22
|
+
end
|
23
|
+
|
24
|
+
view.repaint(stylesheet || UnderOs::App.history.current_page.stylesheet)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def cleanup
|
29
|
+
# implement me if you need to clean cells between reuse
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
#
|
2
|
+
# The basic, grid (flow) layout. inheritable.
|
3
|
+
#
|
4
|
+
class Layout
|
5
|
+
attr_reader :_
|
6
|
+
|
7
|
+
def initialize(layout=nil)
|
8
|
+
@_ = layout || UICollectionViewFlowLayout.alloc.init
|
9
|
+
|
10
|
+
self.items_spacing = 0
|
11
|
+
self.rows_spacing = 1
|
12
|
+
end
|
13
|
+
|
14
|
+
def item_size
|
15
|
+
end
|
16
|
+
|
17
|
+
def item_size=(*size)
|
18
|
+
size = UnderOs::Point.new(*size)
|
19
|
+
@_.itemSize = CGSizeMake(size.x, size.y)
|
20
|
+
end
|
21
|
+
|
22
|
+
def items_spacing
|
23
|
+
@_.minimumInteritemSpacing
|
24
|
+
end
|
25
|
+
|
26
|
+
def items_spacing=(value)
|
27
|
+
@_.minimumInteritemSpacing = value
|
28
|
+
end
|
29
|
+
|
30
|
+
def rows_spacing
|
31
|
+
@_.minimumLineSpacing
|
32
|
+
end
|
33
|
+
|
34
|
+
def rows_spacing=(value)
|
35
|
+
@_.minimumLineSpacing = value
|
36
|
+
end
|
37
|
+
|
38
|
+
def section_inset
|
39
|
+
end
|
40
|
+
|
41
|
+
def section_inset=(value)
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
#
|
2
|
+
# This thing catches styles from the collection element
|
3
|
+
# and adjusts the layout settings for them
|
4
|
+
#
|
5
|
+
class UnderOs::UI::Collection::Styles
|
6
|
+
def self.build(collection, stylesheet)
|
7
|
+
item = UnderOs::UI::Collection::Item.for(collection, stylesheet)
|
8
|
+
|
9
|
+
collection.layout.item_size = item.size
|
10
|
+
collection.layout.items_spacing = item.style.marginLeft + item.style.marginRight
|
11
|
+
collection.layout.rows_spacing = item.style.marginTop + item.style.marginBottom
|
12
|
+
|
13
|
+
# TODO header/footer
|
14
|
+
end
|
15
|
+
end
|