motion-xray 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/.gitignore +21 -0
  2. data/Gemfile +3 -0
  3. data/Gemfile.lock +28 -0
  4. data/README.md +426 -0
  5. data/Rakefile +11 -0
  6. data/app/app_delegate.rb +44 -0
  7. data/lib/motion-xray.rb +37 -0
  8. data/lib/motion-xray/plugins/accessibility_plugin.rb +129 -0
  9. data/lib/motion-xray/plugins/log_plugin.rb +301 -0
  10. data/lib/motion-xray/plugins/save_ui_plugin.rb +142 -0
  11. data/lib/motion-xray/plugins/ui_plugin.rb +41 -0
  12. data/lib/motion-xray/version.rb +5 -0
  13. data/lib/motion-xray/views/xray_color_swatch.rb +234 -0
  14. data/lib/motion-xray/views/xray_dpad.rb +142 -0
  15. data/lib/motion-xray/views/xray_gradient_view.rb +23 -0
  16. data/lib/motion-xray/views/xray_headers.rb +101 -0
  17. data/lib/motion-xray/views/xray_lock_button.rb +50 -0
  18. data/lib/motion-xray/views/xray_scroll_view.rb +12 -0
  19. data/lib/motion-xray/views/xray_toolbar.rb +173 -0
  20. data/lib/motion-xray/views/xray_window.rb +13 -0
  21. data/lib/motion-xray/xray.rb +56 -0
  22. data/lib/motion-xray/xray_constants.rb +5 -0
  23. data/lib/motion-xray/xray_dummy.rb +8 -0
  24. data/lib/motion-xray/xray_editors.rb +62 -0
  25. data/lib/motion-xray/xray_ext.rb +125 -0
  26. data/lib/motion-xray/xray_plugin.rb +40 -0
  27. data/lib/motion-xray/xray_typewriter.rb +217 -0
  28. data/lib/motion-xray/xray_ui.rb +723 -0
  29. data/lib/motion-xray/z_editors/xray_boolean_editor.rb +24 -0
  30. data/lib/motion-xray/z_editors/xray_color_editor.rb +119 -0
  31. data/lib/motion-xray/z_editors/xray_frame_editor.rb +108 -0
  32. data/lib/motion-xray/z_editors/xray_image_editor.rb +78 -0
  33. data/lib/motion-xray/z_editors/xray_text_editor.rb +94 -0
  34. data/lib/resources/xray_button_bg@2x.png +0 -0
  35. data/lib/resources/xray_choose_button@2x.png +0 -0
  36. data/lib/resources/xray_clear_button@2x.png +0 -0
  37. data/lib/resources/xray_detail_button@2x.png +0 -0
  38. data/lib/resources/xray_dpad@2x.png +0 -0
  39. data/lib/resources/xray_dpad_center@2x.png +0 -0
  40. data/lib/resources/xray_dpad_down@2x.png +0 -0
  41. data/lib/resources/xray_dpad_left@2x.png +0 -0
  42. data/lib/resources/xray_dpad_right@2x.png +0 -0
  43. data/lib/resources/xray_dpad_up@2x.png +0 -0
  44. data/lib/resources/xray_drawer_left@2x.png +0 -0
  45. data/lib/resources/xray_drawer_right@2x.png +0 -0
  46. data/lib/resources/xray_edit_button@2x.png +0 -0
  47. data/lib/resources/xray_email_button@2x.png +0 -0
  48. data/lib/resources/xray_lock_button_horizontal@2x.png +0 -0
  49. data/lib/resources/xray_lock_button_locked@2x.png +0 -0
  50. data/lib/resources/xray_lock_button_unlocked@2x.png +0 -0
  51. data/lib/resources/xray_lock_button_vertical@2x.png +0 -0
  52. data/motion-xray.gemspec +40 -0
  53. data/resources/Default-568h@2x.png +0 -0
  54. data/spec/xray_view_spec.rb +43 -0
  55. data/vendor/Podfile.lock +11 -0
  56. metadata +177 -0
@@ -0,0 +1,62 @@
1
+ module Motion ; module Xray
2
+
3
+ class Editor
4
+ attr_accessor :target
5
+ attr_accessor :property
6
+
7
+ class << self
8
+ def with_target(target, property:property)
9
+ self.new(target, property)
10
+ end
11
+ end
12
+
13
+ def initialize(target, property)
14
+ @target = target
15
+ @property = property
16
+ end
17
+
18
+ def did_change?
19
+ false
20
+ end
21
+
22
+ def get_edit_view(container_width)
23
+ @edit_view ||= self.edit_view(container_width)
24
+ end
25
+
26
+ def did_change?
27
+ true
28
+ end
29
+
30
+ end
31
+
32
+ class PropertyEditor < Editor
33
+
34
+ def initialize(target, property)
35
+ super
36
+ @original = get_value
37
+ end
38
+
39
+ def get_value
40
+ if target.respond_to?(property)
41
+ return target.send(property)
42
+ elsif target.respond_to?("#{property}?")
43
+ value = target.send("#{property}?")
44
+ return target.send("#{property}?")
45
+ end
46
+ end
47
+
48
+ def set_value(value)
49
+ assign = "#{property}="
50
+ setter = "set#{property.sub(/^./) { |c| c.upcase }}"
51
+
52
+ if target.respond_to?(assign)
53
+ target.send(assign, value)
54
+ elsif target.respond_to?(setter)
55
+ target.send(setter, value)
56
+ end
57
+ XrayTargetDidChangeNotification.post_notification(@target, { 'property' => @property, 'value' => value, 'original' => @original })
58
+ end
59
+
60
+ end
61
+
62
+ end end
@@ -0,0 +1,125 @@
1
+ class UIImage
2
+ class << self
3
+ alias :imageNamed_xray_old :imageNamed
4
+ def imageNamed(name)
5
+ imageNamed_xray_old(name)
6
+ end
7
+ end
8
+ end
9
+
10
+ class UIView
11
+
12
+ class << self
13
+ attr_accessor :xray
14
+
15
+ def xray
16
+ {
17
+ 'Frame' => {
18
+ frame: Motion::Xray::FrameEditor,
19
+ },
20
+ 'Color' => {
21
+ backgroundColor: Motion::Xray::ColorEditor,
22
+ },
23
+ 'UI' => {
24
+ hidden: Motion::Xray::BooleanEditor,
25
+ userInteractionEnabled: Motion::Xray::BooleanEditor,
26
+ accessibilityLabel: Motion::Xray::TextEditor,
27
+ },
28
+ }
29
+ end
30
+
31
+ # this could be optimized a tiny bit by only calling superclass.build_xray
32
+ # but i am le tired
33
+ def build_xray
34
+ @build_xray ||= begin
35
+ retval = Hash.new { |hash,key| hash[key] = {} }
36
+ klasses = []
37
+ klass = self
38
+ while klass && klass <= UIView
39
+ klasses.unshift(klass)
40
+ klass = klass.superclass
41
+ end
42
+
43
+ klasses.each do |klass|
44
+ xray_props = klass.xray
45
+ xray_props && xray_props.each do |key,values|
46
+ values.keys.each do |check_unique|
47
+ retval.each do |section, editors|
48
+ editors.delete(check_unique)
49
+ end
50
+ end
51
+ retval[key].merge!(values)
52
+ end
53
+ end
54
+
55
+ # clean out nil-editors and empty sections
56
+ retval.each do |section, editors|
57
+ editors.each do |property, editor|
58
+ editors.delete(property) unless editor
59
+ end
60
+ retval.delete(section) if editors.length == 0
61
+ end
62
+
63
+ retval
64
+ end
65
+ end
66
+
67
+ end
68
+
69
+ def xray
70
+ self.class.build_xray
71
+ end
72
+
73
+ def xray_subviews
74
+ subviews
75
+ end
76
+
77
+ end
78
+
79
+ class << UIWindow
80
+ def xray
81
+ {
82
+ 'TurnOff' => {
83
+ frame: nil,
84
+ hidden: nil,
85
+ userInteractionEnabled: nil,
86
+ },
87
+ }
88
+ end
89
+ end
90
+
91
+ class << UILabel
92
+ def xray
93
+ {
94
+ 'Content' => {
95
+ text: Motion::Xray::TextEditor,
96
+ }
97
+ }
98
+ end
99
+ end
100
+
101
+ class << UITabBar
102
+ def xray
103
+ {
104
+ 'Color' => {
105
+ tintColor: Motion::Xray::ColorEditor,
106
+ },
107
+ }
108
+ end
109
+ end
110
+
111
+ class << UINavigationBar
112
+ def xray
113
+ {
114
+ 'Color' => {
115
+ tintColor: Motion::Xray::ColorEditor,
116
+ },
117
+ }
118
+ end
119
+ end
120
+
121
+ class UIButton
122
+ def xray_subviews
123
+ []
124
+ end
125
+ end
@@ -0,0 +1,40 @@
1
+ module Motion ; module Xray
2
+
3
+ class Plugin
4
+ attr_accessor :name
5
+ attr :view
6
+ attr :target
7
+
8
+ def Plugin.name(value=nil)
9
+ if value
10
+ @name = value
11
+ else
12
+ @name
13
+ end
14
+ end
15
+
16
+ def xray_name
17
+ @name || self.class.name
18
+ end
19
+
20
+ def plugin_view(canvas)
21
+ raise "You must implement `#{self.class}#plugin_view`"
22
+ end
23
+
24
+ def get_plugin_view(canvas)
25
+ @view ||= plugin_view(canvas)
26
+ end
27
+
28
+ def edit(target)
29
+ @target = target
30
+ end
31
+
32
+ def show
33
+ end
34
+
35
+ def hide
36
+ end
37
+
38
+ end
39
+
40
+ end end
@@ -0,0 +1,217 @@
1
+ module Motion ; module Xray
2
+ # Copied from the gem of the same name, but I didn't want the dependency
3
+ class XrayTypewriterView < UIView
4
+ attr_accessor :scroll_view
5
+ attr_accessor :background_view
6
+
7
+ attr_accessor :vertical_spacing, :horizontal_spacing
8
+ attr_accessor :top_margin, :bottom_margin
9
+ attr_accessor :left_margin, :right_margin
10
+
11
+ attr_accessor :centered
12
+ attr_accessor :min_width, :min_height
13
+
14
+ def initWithFrame(frame)
15
+ super.tap do
16
+ self.spacing = 0
17
+ self.margin = 0
18
+ self.centered = false
19
+ self.min_width = nil
20
+ self.min_height = nil
21
+ self.contentMode = :bottom.uicontentmode
22
+
23
+ @row = []
24
+ end
25
+ end
26
+
27
+ def shrink
28
+ self.frame = self.frame.height(0)
29
+ end
30
+
31
+ def expand
32
+ self.frame = self.frame.height(self.subviews[0].frame.height)
33
+ end
34
+
35
+ def spacing=(spacing)
36
+ if spacing.is_a? Array
37
+ case spacing.length
38
+ when 1
39
+ self.spacing = spacing[0]
40
+ when 2
41
+ @horizontal_spacing = spacing[0]
42
+ @vertical_spacing = spacing[1]
43
+ end
44
+ else
45
+ @horizontal_spacing = @vertical_spacing = spacing
46
+ end
47
+ end
48
+
49
+ def margin=(margins)
50
+ margins = [margins] unless margins.is_a? Enumerable
51
+
52
+ case margins.length
53
+ when 1
54
+ @top_margin = @bottom_margin = @left_margin = @right_margin = margins[0]
55
+ when 2
56
+ @top_margin = margins[0]
57
+ @bottom_margin = margins[0]
58
+ @left_margin = margins[1]
59
+ @right_margin = margins[1]
60
+ when 3
61
+ @top_margin = margins[0]
62
+ @bottom_margin = margins[0]
63
+ @right_margin = margins[1]
64
+ @left_margin = margins[2]
65
+ when 4
66
+ @top_margin = margins[0]
67
+ @right_margin = margins[1]
68
+ @bottom_margin = margins[2]
69
+ @left_margin = margins[3]
70
+ else
71
+ raise "Too many arguments (#{margins.length}) sent to MarginView#margin"
72
+ end
73
+ end
74
+
75
+ ##| DEFAULTS
76
+ def vertical_spacing
77
+ @vertical_spacing ||= 0
78
+ @vertical_spacing
79
+ end
80
+ def horizontal_spacing
81
+ @horizontal_spacing ||= 0
82
+ @horizontal_spacing
83
+ end
84
+ def top_margin
85
+ @top_margin ||= 0
86
+ @top_margin
87
+ end
88
+ def bottom_margin
89
+ @bottom_margin ||= 0
90
+ @bottom_margin
91
+ end
92
+ def left_margin
93
+ @left_margin ||= 0
94
+ @left_margin
95
+ end
96
+ def right_margin
97
+ @right_margin ||= 0
98
+ @right_margin
99
+ end
100
+
101
+ ##|
102
+ ##| START AT 0, 0, AND START FLOATING
103
+ ##|
104
+ def layoutSubviews
105
+ super
106
+ # super
107
+ # the max_height of *all* the rows so far (not just the current row)
108
+ @max_height = top_margin
109
+ clear
110
+
111
+ # when a row would be longer than this, it is wrapped to the next row.
112
+ @max_x = self.frame.size.width - right_margin
113
+
114
+ self.subviews.each do |view|
115
+ unless view == @background_view
116
+ view.setNeedsLayout
117
+ view.layoutIfNeeded
118
+ add_next(view)
119
+ end
120
+ end
121
+ clear(true) # don't add vertical spacing
122
+
123
+ self.frame = [self.frame.origin, [self.frame.size.width, @y + bottom_margin]]
124
+ if scroll_view
125
+ scroll_view.scrollEnabled = (@y > scroll_view.frame.size.height)
126
+ scroll_view.contentSize = self.frame.size
127
+ end
128
+ if background_view
129
+ background_view.frame = self.bounds
130
+ end
131
+ end
132
+
133
+ def setNeedsLayout
134
+ super
135
+ end
136
+
137
+ def background_view=(view)
138
+ @background_view.removeFromSuperview if @background_view && @background_view.superview == self
139
+ view.frame = self.bounds
140
+ self.insertSubview(view, atIndex:0)
141
+ @background_view = view
142
+ end
143
+
144
+ private
145
+ def clear(is_last_row=false)
146
+ @x = left_margin
147
+ @y = @max_height
148
+ # only add the horizontal_spacing after at least one row has been written
149
+ if @y > top_margin && ! is_last_row
150
+ @y += vertical_spacing
151
+ end
152
+
153
+ if self.centered
154
+ row_width = left_margin
155
+ row_height = min_height || 0
156
+ @row.each do |view|
157
+ if row_width > 0
158
+ row_width += horizontal_spacing
159
+ end
160
+ row_height = view.frame.size.height if view.frame.size.height > row_height
161
+ row_width += view.frame.size.width
162
+ end
163
+ row_width += right_margin
164
+ row_width = min_width if min_width and row_width < min_width
165
+ x = ((self.frame.size.width - row_width) / 2).round
166
+
167
+ @row.each do |view|
168
+ frame = view.frame
169
+
170
+ y = ((row_height - frame.size.height) / 2).round
171
+
172
+ if x > 0 or y > 0
173
+ frame.origin.x += x
174
+ frame.origin.y += y
175
+ view.frame = frame
176
+ end
177
+ end
178
+ end
179
+ @row = []
180
+ end
181
+
182
+ def add_next(subview)
183
+ # this frame will get modified and reassigned
184
+ subview_frame = subview.frame
185
+
186
+ # move to the next new row?
187
+ width = subview_frame.size.width
188
+ if self.min_width and width < self.min_width
189
+ width = self.min_width
190
+ end
191
+
192
+ next_x = @x + width
193
+ # too big?
194
+ if next_x > @max_x
195
+ clear
196
+ next_x = @x + width
197
+ end
198
+
199
+ # new max_height?
200
+ height = subview_frame.size.height
201
+ if self.min_height and height < self.min_height
202
+ height = self.min_height
203
+ end
204
+ next_y = @y + height
205
+ @max_height = next_y if next_y > @max_height
206
+
207
+ subview_frame.origin.x = @x
208
+ subview_frame.origin.y = @y
209
+ @x = next_x + horizontal_spacing
210
+
211
+ subview.frame = subview_frame
212
+ @row.push subview
213
+ end
214
+
215
+ end
216
+
217
+ end end
@@ -0,0 +1,723 @@
1
+ module Motion ; module Xray
2
+
3
+ class XrayViewController < UIViewController
4
+
5
+ def loadView
6
+ self.view = Xray.ui.teh_ui
7
+ end
8
+
9
+ def supportedInterfaceOrientations
10
+ UIInterfaceOrientationPortrait
11
+ end
12
+
13
+ def shouldAutorotate
14
+ false
15
+ end
16
+
17
+ def willAnimateRotationToInterfaceOrientation(orientation, duration:duration)
18
+ Xray.ui.update_orientation
19
+ end
20
+
21
+ end
22
+
23
+ class UI
24
+ attr :teh_ui
25
+ attr :tiny_view
26
+ attr :expand_button
27
+ attr :assign_button
28
+ attr :top_bar
29
+ attr :bottom_bar
30
+ attr :bottom_half
31
+ attr :canvas
32
+ attr :label
33
+ attr :revert
34
+ attr :selected
35
+ attr :target
36
+ attr :cover
37
+ attr :selector
38
+ attr :subviews
39
+ attr :superview
40
+ attr :table
41
+ attr :table_source
42
+
43
+ def toggle
44
+ if fired?
45
+ cool_down
46
+ else
47
+ fire_up
48
+ end
49
+ return fired?
50
+ end
51
+
52
+ def full_screen_width
53
+ Xray.window.bounds.width
54
+ end
55
+
56
+ def full_screen_height
57
+ Xray.window.bounds.height
58
+ end
59
+
60
+ def half_screen_width
61
+ full_screen_width / 2
62
+ end
63
+
64
+ def half_screen_height
65
+ Xray.window.bounds.height / 2
66
+ end
67
+
68
+ def bottom_half_height
69
+ full_screen_height - half_screen_height
70
+ end
71
+
72
+ def bar_height
73
+ 30
74
+ end
75
+
76
+ def bottom_half_top
77
+ half_screen_height
78
+ end
79
+
80
+ def toolbar_top
81
+ bottom_half_height - 25
82
+ end
83
+
84
+ def toolbar_height
85
+ 25
86
+ end
87
+
88
+ def canvas_top
89
+ 0
90
+ end
91
+
92
+ def canvas_height
93
+ bottom_half_height - toolbar_height
94
+ end
95
+
96
+ def fired?
97
+ @fired
98
+ end
99
+
100
+ def build_the_ui
101
+ unless @teh_ui
102
+ @teh_ui = UIView.alloc.initWithFrame(Xray.window.bounds)
103
+ @xray_controller = Xray.controller
104
+
105
+ @tiny_view = UIView.alloc.initWithFrame([[0, 0], [half_screen_width, half_screen_height]])
106
+ @teh_ui << @tiny_view
107
+
108
+ @cover = UIControl.alloc.initWithFrame([[0, 0], [half_screen_width, half_screen_height]])
109
+ @cover.on :touch {
110
+ Xray.cool_down
111
+ 2.seconds.later do
112
+ Xray.fire_up
113
+ end
114
+ }
115
+ @teh_ui << @cover
116
+
117
+ @selector = UIView.alloc.init
118
+ @selector.userInteractionEnabled = false
119
+ @selector.layer.borderWidth = 1
120
+ @selector.layer.borderColor = '#a91105'.uicolor.cgcolor
121
+ @selector.layer.opacity = 0
122
+ @teh_ui << @selector
123
+
124
+ @top_half = UIView.alloc.initWithFrame([[half_screen_width, 0], [half_screen_width, half_screen_height]])
125
+ @teh_ui << @top_half
126
+
127
+ @table = UITableView.alloc.initWithFrame(CGRect.empty, style: :plain.uitableviewstyle)
128
+ @table.frame = [[0, bar_height], [half_screen_width, half_screen_height - bar_height * 2]]
129
+ @table.rowHeight = 30
130
+ @table.delegate = self
131
+ @table.autoresizingMask = :full.uiautoresizemask
132
+ @top_half << @table
133
+
134
+ @top_bar = XrayHeaderBackground.alloc.initWithFrame([[0, 0], [half_screen_width, bar_height]])
135
+ @top_bar.autoresizingMask = :fixed_top.uiautoresizemask
136
+ @top_bar.label = XrayHeaderLabel.alloc.initWithFrame(@top_bar.bounds.right(30).thinner(30))
137
+ @top_bar.label.autoresizingMask = :flexible_width.uiautoresizemask
138
+
139
+ @expand_button = XrayDetailButton.alloc.init
140
+ @expand_button.transform = CGAffineTransformMakeRotation(180.degrees)
141
+ @expand_button.on :touch {
142
+ toggle_picker
143
+ }
144
+ @top_bar << @expand_button
145
+
146
+ @choose_button = UIButton.custom
147
+ @choose_button.setImage('xray_choose_button'.uiimage, forState: :normal.uicontrolstate)
148
+ @choose_button.sizeToFit
149
+ f = @choose_button.frame
150
+ f.origin = @top_bar.bounds.top_right + CGPoint.new(-4 - f.width, 4)
151
+ @choose_button.frame = f
152
+ @choose_button.on :touch {
153
+ choose_view
154
+ }
155
+ @choose_button.autoresizingMask = :fixed_top_right.uiautoresizemask
156
+ @top_bar << @choose_button
157
+
158
+ @top_half << @top_bar
159
+
160
+ @bottom_bar = XrayHeaderBackground.alloc.initWithFrame([[0, half_screen_height - bar_height], [half_screen_width, bar_height]])
161
+ @bottom_bar.label = XrayHeaderLabel.alloc.initWithFrame(@bottom_bar.bounds.right(3).thinner(33))
162
+ @bottom_bar.label.autoresizingMask = :flexible_width.uiautoresizemask
163
+ @bottom_bar.autoresizingMask = :fixed_bottom.uiautoresizemask
164
+ @top_half << @bottom_bar
165
+
166
+ @assign_button = XrayDetailButton.alloc.init
167
+ @assign_button.transform = CGAffineTransformMakeRotation(90.degrees)
168
+ @assign_button.frame = @assign_button.frame.x(half_screen_width - @assign_button.frame.width)
169
+ @assign_button.on :touch {
170
+ edit(@selected) if @selected
171
+ }
172
+ @assign_button.autoresizingMask = :fixed_bottom_right.uiautoresizemask
173
+ @bottom_bar << @assign_button
174
+
175
+ @bottom_half = UIView.alloc.initWithFrame([[0, bottom_half_top], [full_screen_width, bottom_half_height]])
176
+ grad_layer = CAGradientLayer.layer
177
+ grad_layer.frame = @bottom_half.layer.bounds
178
+ grad_layer.colors = [:white.uicolor.cgcolor, :lightgray.uicolor.cgcolor]
179
+ @bottom_half.layer << grad_layer
180
+ @teh_ui << @bottom_half
181
+
182
+ @canvas = XrayScrollView.alloc.init
183
+ @canvas.frame = [[0, canvas_top], [full_screen_width, canvas_height]]
184
+ @bottom_half << @canvas
185
+
186
+ @toolbar = PluginToolbar.alloc.initWithFrame([[-1, toolbar_top], [full_screen_width + 2, toolbar_height + 1]])
187
+ @toolbar.canvas = @canvas
188
+ @bottom_half << @toolbar
189
+
190
+ Xray.plugins.each do |plugin|
191
+ @toolbar.add(plugin)
192
+ end
193
+ end
194
+
195
+ @tiny_view.subviews.each &:removeFromSuperview
196
+ end
197
+
198
+ def transition_ui
199
+ @selector.fade_in
200
+
201
+ @top_half.frame = @top_half.frame.x(full_screen_width)
202
+ @top_half.slide :left, half_screen_width
203
+
204
+ @bottom_half.frame = @bottom_half.frame.y(full_screen_height)
205
+ @bottom_half.slide :up, bottom_half_height
206
+ end
207
+
208
+ def fire_up
209
+ return if @fired
210
+ @fired = true
211
+ Xray.window.first_responder && Xray.window.first_responder.resignFirstResponder
212
+
213
+ # gather all window subviews into 'revert_view'
214
+ @revert = {
215
+ views: [],
216
+ status_bar_was_hidden?: Xray.app_shared.statusBarHidden?,
217
+ transforms: {}
218
+ }
219
+
220
+ Xray.window.subviews.each do |subview|
221
+ @revert[:views] << subview
222
+ @revert[:transforms][subview] = subview.layer.transform
223
+ end
224
+ Xray.app_shared.setStatusBarHidden(true, withAnimation:UIStatusBarAnimationSlide)
225
+
226
+ build_the_ui
227
+ @old_controller = Xray.window.rootViewController
228
+ Xray.window.rootViewController = @xray_controller
229
+ @revert[:views].each do |view|
230
+ @tiny_view << view
231
+ end
232
+
233
+ transition_ui
234
+ apply_transform(true)
235
+
236
+ if @selected && ! @selected.isDescendantOfView(Xray.window)
237
+ @selected = nil
238
+ end
239
+ if @target && ! @target.isDescendantOfView(Xray.window)
240
+ @target = nil
241
+ end
242
+
243
+ subviews = view_tree
244
+ @table_source = XrayTableSource.new(@selected || Xray.window, subviews)
245
+ @table.dataSource = @table_source
246
+ @table.delegate = self
247
+
248
+ select(Xray.window) unless @selected
249
+ edit(Xray.window) unless @target
250
+ end
251
+
252
+ def cool_down
253
+ return unless @fired
254
+ @fired = false
255
+
256
+ @selector.fade_out
257
+ @top_half.slide(:right, half_screen_width)
258
+ @bottom_half.slide(:down, bottom_half_height) {
259
+ Xray.window.rootViewController = @old_controller
260
+ @old_controller = nil
261
+
262
+ @teh_ui.removeFromSuperview
263
+ }
264
+
265
+ @revert[:views].each do |subview|
266
+ Xray.window << subview
267
+ UIView.animate {
268
+ # identity matrix
269
+ subview.layer.transform = @revert[:transforms][subview]
270
+ subview.layer.anchorPoint = [0.5, 0.5]
271
+ }
272
+ end
273
+ Xray.app_shared.setStatusBarHidden(@revert[:status_bar_was_hidden?], withAnimation:UIStatusBarAnimationSlide)
274
+ @revert = nil
275
+ end
276
+
277
+ def view_tree(view=nil, indent=nil, depth=0, is_last=true, return_views=[])
278
+ if view
279
+ subviews = view.xray_subviews
280
+ else
281
+ view = Xray.window
282
+ subviews = @revert[:views]
283
+ end
284
+
285
+ if indent
286
+ next_indent = indent.dup
287
+ if is_last
288
+ indent += "╘═ "
289
+ next_indent += ' '
290
+ else
291
+ indent += "╞═ "
292
+ next_indent += "┃ "
293
+ end
294
+ else
295
+ indent = ''
296
+ next_indent = ''
297
+ end
298
+
299
+ return_views << {view: view, indent: indent, depth: depth}
300
+
301
+ subviews.each_with_index { |subview, index|
302
+ view_tree(subview, next_indent, depth + 1, index == subviews.length - 1, return_views)
303
+ }
304
+ return return_views
305
+ end
306
+
307
+ def choose_view
308
+ restore_shown_views = collect_visible_views
309
+
310
+ @choose_view = UIView.alloc.initWithFrame(Xray.window.bounds)
311
+ @choose_view.backgroundColor = :black.uicolor
312
+ @choose_view.opaque = true
313
+ @choose_view.alpha = 0.0
314
+
315
+ controls = @revert[:views].reverse.map { |subview|
316
+ buttony_views(subview)
317
+ }.flatten
318
+
319
+ restore_shown_views.each do |subview|
320
+ subview.show
321
+ end
322
+
323
+ label = UILabel.alloc.initWithFrame([[5, 5], [0, 0]])
324
+ label.backgroundColor = :clear.uicolor
325
+ label.textColor = :white.uicolor
326
+ label.textAlignment = :left.uitextalignment
327
+
328
+ container = UIView.alloc.initWithFrame(CGRect.empty)
329
+ container.layer.cornerRadius = label.frame.height/2
330
+ container.backgroundColor = :black.uicolor(0.5)
331
+ container << label
332
+
333
+ controls.reverse.each do |control|
334
+ control.container = container
335
+ control.label = label
336
+ @choose_view << control
337
+ end
338
+
339
+ @choose_view << container
340
+
341
+ Xray.window << @choose_view
342
+ @choose_view.fade_in
343
+ timer = 0
344
+ end
345
+
346
+ def did_choose_view(view)
347
+ radius = Math.sqrt(Xray.window.bounds.width**2 + Xray.window.bounds.height**2)
348
+ window_center = Xray.window.center
349
+ @choose_view.subviews.each do |subview|
350
+ angle = window_center.angle_to(subview.center)
351
+ random_x = radius * Math.cos(angle)
352
+ random_y = radius * Math.sin(angle)
353
+ subview.move_to([random_x, random_y], 1)
354
+ end
355
+ 0.5.later do
356
+ @choose_view.fade_out_and_remove
357
+ end
358
+ edit(view)
359
+ select(view)
360
+ end
361
+
362
+ def collect_visible_views(view=nil)
363
+ if view
364
+ # join all the subviews
365
+ view.xray_subviews.reverse.map { |subview|
366
+ collect_visible_views(subview)
367
+ }.flatten + [view]
368
+ else
369
+ # start at the revert[:views] and collect all subviews
370
+ @revert[:views].reverse.map { |subview|
371
+ collect_visible_views(subview)
372
+ }.flatten.select { |subview| !subview.hidden? }
373
+ end
374
+ end
375
+
376
+ def buttony_views(view)
377
+ children = view.xray_subviews.reverse.map { |subview|
378
+ buttony_views(subview)
379
+ }.flatten
380
+
381
+ f = view.convertRect(view.bounds, toView:nil)
382
+ f.origin.x *= 2
383
+ f.origin.y *= 2
384
+ f.size.width *= 2
385
+ f.size.height *= 2
386
+
387
+ btn = XrayChooseViewButton.alloc.initWithFrame(view.bounds)
388
+ btn.transform = CGAffineTransformConcat(view.transform, get_transform(false))
389
+ btn.frame = f
390
+ btn.target = view
391
+ btn.on(:touch_down_repeat) {
392
+ did_choose_view(view)
393
+ }
394
+
395
+ btn.children = children
396
+
397
+ btn.setImage(view.uiimage, forState: :normal.uicontrolstate)
398
+ btn.accessibilityLabel = "choose #{view.accessibilityLabel}"
399
+ btn.layer.borderColor = :white.uicolor.cgcolor
400
+ btn.layer.borderWidth = 1
401
+ view.hide
402
+
403
+ children + [btn]
404
+ end
405
+
406
+ def select(selected)
407
+ return unless selected
408
+
409
+ @selected = selected
410
+ @top_bar.text = @selected.class.name
411
+ index = @table_source.subviews.index { |item| item[:view] == @selected }
412
+ index_path = [0, index].nsindexpath
413
+ @table.selectRowAtIndexPath(index_path, animated:true, scrollPosition:UITableViewScrollPositionNone)
414
+ @table.scrollToRowAtIndexPath(index_path, atScrollPosition: UITableViewScrollPositionNone, animated:true)
415
+
416
+ UIView.animate {
417
+ if @selected == Xray.window
418
+ selector_frame = [[0, 0], [half_screen_width, half_screen_height]]
419
+ else
420
+ selector_frame = Xray.window.convertRect(@selected.bounds, fromView:@selected)
421
+ end
422
+ @selector.frame = selector_frame
423
+ }
424
+ end
425
+
426
+ def edit(target)
427
+ collapse_picker
428
+ @bottom_bar.text = target.to_s
429
+
430
+ Xray.plugins.each do |plugin|
431
+ plugin.edit(target)
432
+ end
433
+ @target = target
434
+ SugarCube::Adjust::adjust(target)
435
+ reset
436
+ end
437
+
438
+ def reset
439
+ @toolbar.show
440
+ @canvas.contentOffset = [0, 0]
441
+ end
442
+
443
+ def toggle_picker
444
+ if @picker_is_expanded
445
+ collapse_picker
446
+ else
447
+ expand_picker
448
+ end
449
+ end
450
+
451
+ def expand_picker
452
+ return if @picker_is_expanded
453
+ UIView.animate {
454
+ @top_half.frame = [[0, 0], [full_screen_width, half_screen_height]]
455
+ @expand_button.transform = CGAffineTransformMakeRotation(0.degrees)
456
+ }
457
+ @picker_is_expanded = true
458
+ end
459
+
460
+ def collapse_picker
461
+ return unless @picker_is_expanded
462
+ UIView.animation_chain {
463
+ @top_half.frame = [[half_screen_width, 0], [full_screen_width, half_screen_height]]
464
+ @expand_button.transform = CGAffineTransformMakeRotation(180.degrees)
465
+ }.and_then {
466
+ @top_half.frame = [[half_screen_width, 0], [half_screen_width, half_screen_height]]
467
+ }.start
468
+ @picker_is_expanded = false
469
+ end
470
+
471
+ def tableView(table_view, didSelectRowAtIndexPath:index_path)
472
+ table_selection = @table_source.subviews[index_path.row][:view]
473
+ if @selected == table_selection
474
+ edit(table_selection)
475
+ else
476
+ select(table_selection)
477
+ end
478
+ end
479
+
480
+ def update_orientation(animate=true)
481
+ case UIApplication.sharedApplication.statusBarOrientation
482
+ when UIInterfaceOrientationPortrait
483
+ when UIInterfaceOrientationPortraitUpsideDown
484
+ when UIInterfaceOrientationLandscapeLeft
485
+ when UIInterfaceOrientationLandscapeRight
486
+ end
487
+
488
+ apply_transform
489
+ select(@selected)
490
+ edit(@target)
491
+ end
492
+
493
+ def get_transform(scale=true)
494
+ if scale
495
+ dx = -Xray.app_bounds.width / 4
496
+ dy = -Xray.app_bounds.height / 4
497
+ teh_transform = CGAffineTransformMakeTranslation(dx, dy)
498
+ teh_transform = CGAffineTransformScale(teh_transform, 0.5, 0.5)
499
+ else
500
+ teh_transform = CGAffineTransformIdentity
501
+ end
502
+
503
+ case UIApplication.sharedApplication.statusBarOrientation
504
+ when UIInterfaceOrientationPortraitUpsideDown
505
+ teh_transform = CGAffineTransformRotate(teh_transform, 180.degrees)
506
+ when UIInterfaceOrientationLandscapeLeft
507
+ teh_transform = CGAffineTransformRotate(teh_transform, -90.degrees)
508
+ when UIInterfaceOrientationLandscapeRight
509
+ teh_transform = CGAffineTransformRotate(teh_transform, 90.degrees)
510
+ end
511
+
512
+ return teh_transform
513
+ end
514
+
515
+ def apply_transform(animate=true)
516
+ teh_transform = get_transform
517
+
518
+ UIView.animate(duration: animate ? nil : 0) do
519
+ @revert[:views].each do |subview|
520
+ subview.transform = teh_transform
521
+ end
522
+ end
523
+ end
524
+
525
+ def get_screenshot
526
+ scale = UIScreen.mainScreen.scale
527
+ UIGraphicsBeginImageContextWithOptions(Xray.window.bounds.size, false, scale)
528
+ context = UIGraphicsGetCurrentContext()
529
+
530
+ @revert[:views].each do |subview|
531
+ CGContextSaveGState(context)
532
+ CGContextTranslateCTM(context, subview.frame.origin.x, subview.frame.origin.y)
533
+
534
+ subview.layer.renderInContext(context)
535
+ CGContextRestoreGState(context)
536
+ end
537
+ image = UIGraphicsGetImageFromCurrentImageContext()
538
+ UIGraphicsEndImageContext()
539
+ return image
540
+ end
541
+
542
+ end
543
+
544
+
545
+ class XrayTableSource
546
+ attr :selected
547
+ attr :subviews
548
+
549
+ def initialize(selected, subviews)
550
+ @selected = selected
551
+ @subviews = subviews
552
+ end
553
+
554
+ def [](index)
555
+ @subviews[index]
556
+ end
557
+
558
+ ##|
559
+ ##| TABLEVIEW
560
+ ##|
561
+ def numberOfSectionsInTableView(table_view)
562
+ 1
563
+ end
564
+
565
+ def tableView(table_view, numberOfRowsInSection:section)
566
+ @subviews.length
567
+ end
568
+
569
+ def tableView(table_view, cellForRowAtIndexPath:index_path)
570
+ cell_identifier = "XrayTableCell"
571
+ cell = table_view.dequeueReusableCellWithIdentifier(cell_identifier)
572
+
573
+ unless cell
574
+ cell = XrayTableCell.alloc.initWithStyle(:default.uitablecellstyle,
575
+ reuseIdentifier: cell_identifier)
576
+ end
577
+
578
+ view_info = @subviews[index_path.row]
579
+ view = view_info[:view]
580
+ cell.view = view
581
+ cell.depth = view_info[:depth]
582
+ text = ''
583
+ indent = view_info[:indent]
584
+ text << indent << view.to_s
585
+ cell.textLabel.text = text
586
+ cell.row = index_path.row
587
+ cell.detail_button.off :touch
588
+ cell.detail_button.hide
589
+
590
+ return cell
591
+ end
592
+
593
+ end
594
+
595
+
596
+ class XrayTableCell < UITableViewCell
597
+ attr_accessor :view
598
+ attr_accessor :row
599
+ attr :detail_button
600
+ attr_accessor :depth
601
+
602
+ def initWithStyle(style, reuseIdentifier:identifier)
603
+ super.tap do
604
+ textLabel.font = :monospace.uifont(10)
605
+ textLabel.lineBreakMode = :clip.uilinebreakmode
606
+ @detail_button = XrayDetailButton.alloc.init
607
+ @detail_button.frame = [[143, -0.5], [17, 19]]
608
+ contentView << @detail_button
609
+
610
+ self.detail_button.on :touch {
611
+ Xray.ui.select(self.view) if self.view
612
+ }
613
+ end
614
+ end
615
+
616
+ def accessibilityLabel
617
+ "depth #{depth}, instance of #{view.class}" + (view.superview ? ", child of #{view.superview.class}" : '')
618
+ end
619
+
620
+ end
621
+
622
+ class XrayDetailButton < UIButton
623
+
624
+ def init
625
+ initWithFrame([[0, 0], [27, 29]])
626
+ end
627
+
628
+ def initWithFrame(frame)
629
+ super.tap do
630
+ setImage('xray_detail_button'.uiimage, forState: :normal.uicontrolstate)
631
+ end
632
+ end
633
+
634
+ def pointInside(point, withEvent:event)
635
+ bounds.contains?(point)
636
+ end
637
+
638
+ end
639
+
640
+ class XrayChooseViewButton < UIButton
641
+ attr_accessor :container
642
+ attr_accessor :label
643
+ attr_accessor :target
644
+ attr_accessor :children
645
+
646
+ def initWithFrame(frame)
647
+ super.tap do
648
+ self.backgroundColor = :clear.uicolor
649
+ @@fade_out_timer = nil
650
+ @@slide_out_timer = nil
651
+
652
+ self.on(:touch_down) {
653
+ start_touch
654
+ }
655
+ self.on(:touch_stop) {
656
+ stop_touch
657
+ }
658
+ end
659
+ end
660
+
661
+ def slide_out_timer=(timer)
662
+ if @@slide_out_timer
663
+ @@slide_out_timer.invalidate
664
+ end
665
+ @@slide_out_timer = timer
666
+ end
667
+
668
+ def fade_out_timer=(timer)
669
+ if @@fade_out_timer
670
+ @@fade_out_timer.invalidate
671
+ end
672
+ @@fade_out_timer = timer
673
+ end
674
+
675
+ def start_touch
676
+ if @@fade_out_timer
677
+ self.fade_out_timer = nil
678
+ @container.alpha = 1
679
+ else
680
+ @container.alpha = 0
681
+ @container.fade_in
682
+ end
683
+
684
+ @label.text = target.inspect
685
+ @label.sizeToFit
686
+ @label.frame = @label.frame.width([Xray.window.frame.width - 10, @label.frame.width].min)
687
+ @container.frame = @label.bounds.grow(5)
688
+ @container.center = @container.superview.center
689
+
690
+ self.backgroundColor = :black.uicolor(0.5)
691
+ self.slide_out_timer = 1.second.later do
692
+ stop_touch
693
+ slide_out
694
+ @@slide_out_timer = nil
695
+ end
696
+ end
697
+
698
+ def stop_touch
699
+ self.backgroundColor = :clear.uicolor
700
+ self.slide_out_timer = nil
701
+ self.fade_out_timer = 1.second.later do
702
+ @container.fade_out
703
+ @@fade_out_timer = nil
704
+ end
705
+ end
706
+
707
+ def slide_out
708
+ if target.superview == Xray.ui.tiny_view
709
+ Xray.ui.did_choose_view(Xray.window)
710
+ else
711
+ self.children.each do |child|
712
+ child.slide_out
713
+ end
714
+ self.children = []
715
+ self.slide(:up, Xray.window.bounds.width) {
716
+ self.removeFromSuperview
717
+ }
718
+ end
719
+ end
720
+
721
+ end
722
+
723
+ end end