motion-xray 1.0.4

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 (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