rubymotionr 0.0.1
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.
- data/.gitignore +2 -0
- data/.rspec +2 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +24 -0
- data/LICENSE.txt +22 -0
- data/README.md +31 -0
- data/Rakefile +1 -0
- data/lib/rubymotionr.rb +7 -0
- data/lib/rubymotionr/project.rb +18 -0
- data/lib/rubymotionr/repo.rb +18 -0
- data/lib/rubymotionr/version.rb +3 -0
- data/rubymotionr.gemspec +19 -0
- data/spec/.DS_Store +0 -0
- data/spec/multiple-rubymotion-apps/Beers/README +5 -0
- data/spec/multiple-rubymotion-apps/Beers/Rakefile +8 -0
- data/spec/multiple-rubymotion-apps/Beers/app/app_delegate.rb +16 -0
- data/spec/multiple-rubymotion-apps/Beers/app/beer.rb +22 -0
- data/spec/multiple-rubymotion-apps/Beers/app/beer_details_controller.rb +15 -0
- data/spec/multiple-rubymotion-apps/Beers/app/beer_list_controller.rb +40 -0
- data/spec/multiple-rubymotion-apps/Beers/app/beer_map_controller.rb +51 -0
- data/spec/multiple-rubymotion-apps/Beers/resources/.gitignore +0 -0
- data/spec/multiple-rubymotion-apps/Beers/resources/list.png +0 -0
- data/spec/multiple-rubymotion-apps/Beers/resources/map.png +0 -0
- data/spec/multiple-rubymotion-apps/CircleLayout/.gitignore +5 -0
- data/spec/multiple-rubymotion-apps/CircleLayout/README +5 -0
- data/spec/multiple-rubymotion-apps/CircleLayout/Rakefile +9 -0
- data/spec/multiple-rubymotion-apps/CircleLayout/app/app_delegate.rb +8 -0
- data/spec/multiple-rubymotion-apps/CircleLayout/app/cell.rb +13 -0
- data/spec/multiple-rubymotion-apps/CircleLayout/app/circle_layout.rb +41 -0
- data/spec/multiple-rubymotion-apps/CircleLayout/app/view_controller.rb +33 -0
- data/spec/multiple-rubymotion-apps/Fonts/README +7 -0
- data/spec/multiple-rubymotion-apps/Fonts/Rakefile +8 -0
- data/spec/multiple-rubymotion-apps/Fonts/app/app_delegate.rb +9 -0
- data/spec/multiple-rubymotion-apps/Fonts/app/detail_controller.rb +50 -0
- data/spec/multiple-rubymotion-apps/Fonts/app/root_controller.rb +65 -0
- data/spec/multiple-rubymotion-apps/Fonts/resources/.gitkeep +0 -0
- data/spec/multiple-rubymotion-apps/GestureTable/README +8 -0
- data/spec/multiple-rubymotion-apps/GestureTable/Rakefile +8 -0
- data/spec/multiple-rubymotion-apps/GestureTable/app/UIColor_extensions.rb +32 -0
- data/spec/multiple-rubymotion-apps/GestureTable/app/UITableView_extensions.rb +7 -0
- data/spec/multiple-rubymotion-apps/GestureTable/app/app_delegate.rb +9 -0
- data/spec/multiple-rubymotion-apps/GestureTable/app/gesture_recognizer.rb +361 -0
- data/spec/multiple-rubymotion-apps/GestureTable/app/transformable_cells.rb +92 -0
- data/spec/multiple-rubymotion-apps/GestureTable/app/view_controller.rb +173 -0
- data/spec/multiple-rubymotion-apps/GestureTable/resources/.gitignore +0 -0
- data/spec/multiple-rubymotion-apps/Hello/README +3 -0
- data/spec/multiple-rubymotion-apps/Hello/Rakefile +8 -0
- data/spec/multiple-rubymotion-apps/Hello/app/app_delegate.rb +9 -0
- data/spec/multiple-rubymotion-apps/Hello/app/hello_view.rb +33 -0
- data/spec/multiple-rubymotion-apps/Hello/app/hello_view_controller.rb +5 -0
- data/spec/multiple-rubymotion-apps/Hello/resources/.gitignore +0 -0
- data/spec/multiple-rubymotion-apps/Hello/resources/icon.png +0 -0
- data/spec/multiple-rubymotion-apps/HelloGL/README +9 -0
- data/spec/multiple-rubymotion-apps/HelloGL/Rakefile +11 -0
- data/spec/multiple-rubymotion-apps/HelloGL/app/app_delegate.rb +16 -0
- data/spec/multiple-rubymotion-apps/HelloGL/app/index_data.rb +19 -0
- data/spec/multiple-rubymotion-apps/HelloGL/app/motiongl_controller.rb +139 -0
- data/spec/multiple-rubymotion-apps/HelloGL/app/vertex_data.rb +22 -0
- data/spec/multiple-rubymotion-apps/HelloGL/resources/rubymotion.png +0 -0
- data/spec/multiple-rubymotion-apps/Locations/README +3 -0
- data/spec/multiple-rubymotion-apps/Locations/Rakefile +8 -0
- data/spec/multiple-rubymotion-apps/Locations/app/app_delegate.rb +11 -0
- data/spec/multiple-rubymotion-apps/Locations/app/location.rb +21 -0
- data/spec/multiple-rubymotion-apps/Locations/app/locations_controller.rb +66 -0
- data/spec/multiple-rubymotion-apps/Locations/app/locations_store.rb +61 -0
- data/spec/multiple-rubymotion-apps/Locations/resources/.gitignore +0 -0
- data/spec/multiple-rubymotion-apps/Mustache/README +5 -0
- data/spec/multiple-rubymotion-apps/Mustache/Rakefile +10 -0
- data/spec/multiple-rubymotion-apps/Mustache/app/app_delegate.rb +9 -0
- data/spec/multiple-rubymotion-apps/Mustache/app/mustache_view_controller.rb +99 -0
- data/spec/multiple-rubymotion-apps/Mustache/resources/.gitignore +0 -0
- data/spec/multiple-rubymotion-apps/Mustache/resources/guido.jpg +0 -0
- data/spec/multiple-rubymotion-apps/Mustache/resources/jmccolor.jpg +0 -0
- data/spec/multiple-rubymotion-apps/Mustache/resources/kay.jpg +0 -0
- data/spec/multiple-rubymotion-apps/Mustache/resources/matz.jpg +0 -0
- data/spec/multiple-rubymotion-apps/Mustache/resources/mustache.png +0 -0
- data/spec/multiple-rubymotion-apps/Paint/README +3 -0
- data/spec/multiple-rubymotion-apps/Paint/Rakefile +8 -0
- data/spec/multiple-rubymotion-apps/Paint/app/app_delegate.rb +9 -0
- data/spec/multiple-rubymotion-apps/Paint/app/paint_view.rb +57 -0
- data/spec/multiple-rubymotion-apps/Paint/app/paint_view_controller.rb +13 -0
- data/spec/multiple-rubymotion-apps/Paint/resources/.gitignore +0 -0
- data/spec/multiple-rubymotion-apps/Paint/resources/erase.caf +0 -0
- data/spec/multiple-rubymotion-apps/PaintHTML/README +8 -0
- data/spec/multiple-rubymotion-apps/PaintHTML/Rakefile +10 -0
- data/spec/multiple-rubymotion-apps/PaintHTML/app/app_delegate.rb +10 -0
- data/spec/multiple-rubymotion-apps/PaintHTML/app/web_view_controller.rb +48 -0
- data/spec/multiple-rubymotion-apps/PaintHTML/resources/index.html +211 -0
- data/spec/multiple-rubymotion-apps/PaintHTML/resources/index2.html +14 -0
- data/spec/multiple-rubymotion-apps/README.rdoc +47 -0
- data/spec/multiple-rubymotion-apps/StoryboardCustoms/README.md +42 -0
- data/spec/multiple-rubymotion-apps/StoryboardCustoms/Rakefile +7 -0
- data/spec/multiple-rubymotion-apps/StoryboardCustoms/app/app_delegate.rb +12 -0
- data/spec/multiple-rubymotion-apps/StoryboardCustoms/app/custom_view_controller1.rb +31 -0
- data/spec/multiple-rubymotion-apps/StoryboardCustoms/app/custom_view_controller2.rb +29 -0
- data/spec/multiple-rubymotion-apps/StoryboardCustoms/app/helpers.rb +4 -0
- data/spec/multiple-rubymotion-apps/StoryboardCustoms/resources/Master.storyboard +127 -0
- data/spec/multiple-rubymotion-apps/TicTacToe/.gitignore +5 -0
- data/spec/multiple-rubymotion-apps/TicTacToe/Rakefile +7 -0
- data/spec/multiple-rubymotion-apps/TicTacToe/app/app_delegate.rb +8 -0
- data/spec/multiple-rubymotion-apps/TicTacToe/app/board.rb +59 -0
- data/spec/multiple-rubymotion-apps/TicTacToe/app/board_view_controller.rb +86 -0
- data/spec/multiple-rubymotion-apps/Timer/README +3 -0
- data/spec/multiple-rubymotion-apps/Timer/Rakefile +7 -0
- data/spec/multiple-rubymotion-apps/Timer/app/app_delegate.rb +9 -0
- data/spec/multiple-rubymotion-apps/Timer/app/timer_controller.rb +38 -0
- data/spec/multiple-rubymotion-apps/Timer/resources/.gitignore +0 -0
- data/spec/multiple-rubymotion-apps/Trollify/README +6 -0
- data/spec/multiple-rubymotion-apps/Trollify/Rakefile +9 -0
- data/spec/multiple-rubymotion-apps/Trollify/app/app_delegate.rb +9 -0
- data/spec/multiple-rubymotion-apps/Trollify/app/image_composition_controller.rb +193 -0
- data/spec/multiple-rubymotion-apps/Trollify/app/uiimage_extension.rb +27 -0
- data/spec/multiple-rubymotion-apps/Trollify/resources/.gitignore +0 -0
- data/spec/multiple-rubymotion-apps/Trollify/resources/ballmer.jpg +0 -0
- data/spec/multiple-rubymotion-apps/Trollify/resources/icon.png +0 -0
- data/spec/multiple-rubymotion-apps/Trollify/resources/icon@2x.png +0 -0
- data/spec/multiple-rubymotion-apps/Trollify/resources/trollface.png +0 -0
- data/spec/multiple-rubymotion-apps/Tweets/README +3 -0
- data/spec/multiple-rubymotion-apps/Tweets/Rakefile +7 -0
- data/spec/multiple-rubymotion-apps/Tweets/app/app_delegate.rb +9 -0
- data/spec/multiple-rubymotion-apps/Tweets/app/data_parser.rb +10 -0
- data/spec/multiple-rubymotion-apps/Tweets/app/json_parser.rb +12 -0
- data/spec/multiple-rubymotion-apps/Tweets/app/tweet.rb +11 -0
- data/spec/multiple-rubymotion-apps/Tweets/app/tweet_cell.rb +51 -0
- data/spec/multiple-rubymotion-apps/Tweets/app/tweets_controller.rb +72 -0
- data/spec/multiple-rubymotion-apps/Tweets/resources/.gitignore +0 -0
- data/spec/repo_spec.rb +27 -0
- data/spec/rubymotion-example/.DS_Store +0 -0
- data/spec/rubymotion-example/README +3 -0
- data/spec/rubymotion-example/Rakefile +7 -0
- data/spec/rubymotion-example/app/app_delegate.rb +9 -0
- data/spec/rubymotion-example/app/data_parser.rb +10 -0
- data/spec/rubymotion-example/app/json_parser.rb +12 -0
- data/spec/rubymotion-example/app/tweet.rb +11 -0
- data/spec/rubymotion-example/app/tweet_cell.rb +51 -0
- data/spec/rubymotion-example/app/tweets_controller.rb +72 -0
- data/spec/rubymotion-example/resources/.gitignore +0 -0
- data/spec/spec_helper.rb +9 -0
- metadata +309 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class VertexData
|
|
2
|
+
attr_accessor :ptr
|
|
3
|
+
|
|
4
|
+
def initialize(data)
|
|
5
|
+
@vertex_size = data[0].count
|
|
6
|
+
@num_vertices = data.count
|
|
7
|
+
@ptr = Pointer.new(:float, @vertex_size * @num_vertices)
|
|
8
|
+
set_data(data)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def set_data(vertices)
|
|
12
|
+
vertices.each_with_index do |vertex,idx|
|
|
13
|
+
vertex.each_with_index do |component, component_idx|
|
|
14
|
+
@ptr[idx * @vertex_size + component_idx] = component
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def size
|
|
20
|
+
@vertex_size * @num_vertices
|
|
21
|
+
end
|
|
22
|
+
end
|
|
Binary file
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class AppDelegate
|
|
2
|
+
def application(application, didFinishLaunchingWithOptions:launchOptions)
|
|
3
|
+
nav = UINavigationController.alloc.initWithRootViewController(LocationsController.alloc.init)
|
|
4
|
+
nav.wantsFullScreenLayout = true
|
|
5
|
+
nav.toolbarHidden = true
|
|
6
|
+
@window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
|
|
7
|
+
@window.rootViewController = nav
|
|
8
|
+
@window.makeKeyAndVisible
|
|
9
|
+
true
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
class Location < NSManagedObject
|
|
2
|
+
def self.entity
|
|
3
|
+
@entity ||= begin
|
|
4
|
+
# Create the entity for our Location class. The entity has 3 properties. CoreData will appropriately define accessor methods for the properties.
|
|
5
|
+
entity = NSEntityDescription.alloc.init
|
|
6
|
+
entity.name = 'Location'
|
|
7
|
+
entity.managedObjectClassName = 'Location'
|
|
8
|
+
entity.properties =
|
|
9
|
+
['creation_date', NSDateAttributeType,
|
|
10
|
+
'latitude', NSDoubleAttributeType,
|
|
11
|
+
'longitude', NSDoubleAttributeType].each_slice(2).map do |name, type|
|
|
12
|
+
property = NSAttributeDescription.alloc.init
|
|
13
|
+
property.name = name
|
|
14
|
+
property.attributeType = type
|
|
15
|
+
property.optional = false
|
|
16
|
+
property
|
|
17
|
+
end
|
|
18
|
+
entity
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
class LocationsController < UITableViewController
|
|
2
|
+
def viewDidLoad
|
|
3
|
+
view.dataSource = view.delegate = self
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def viewWillAppear(animated)
|
|
7
|
+
navigationItem.title = 'Locations'
|
|
8
|
+
navigationItem.leftBarButtonItem = editButtonItem
|
|
9
|
+
navigationItem.rightBarButtonItem = UIBarButtonItem.alloc.initWithBarButtonSystemItem(UIBarButtonSystemItemAdd, target:self, action:'addLocation')
|
|
10
|
+
|
|
11
|
+
# The Add button is disabled by default, and will be enabled once the location manager is ready to return the current location.
|
|
12
|
+
navigationItem.rightBarButtonItem.enabled = false
|
|
13
|
+
@location_manager ||= CLLocationManager.alloc.init.tap do |lm|
|
|
14
|
+
lm.desiredAccuracy = KCLLocationAccuracyNearestTenMeters
|
|
15
|
+
lm.startUpdatingLocation
|
|
16
|
+
lm.delegate = self
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def addLocation
|
|
21
|
+
LocationsStore.shared.add_location do |location|
|
|
22
|
+
# We set up our new Location object here.
|
|
23
|
+
coordinate = @location_manager.location.coordinate
|
|
24
|
+
location.creation_date = NSDate.date
|
|
25
|
+
location.latitude = coordinate.latitude
|
|
26
|
+
location.longitude = coordinate.longitude
|
|
27
|
+
end
|
|
28
|
+
view.reloadData
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def tableView(tableView, numberOfRowsInSection:section)
|
|
32
|
+
LocationsStore.shared.locations.size
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
CellID = 'CellIdentifier'
|
|
36
|
+
def tableView(tableView, cellForRowAtIndexPath:indexPath)
|
|
37
|
+
cell = tableView.dequeueReusableCellWithIdentifier(CellID) || UITableViewCell.alloc.initWithStyle(UITableViewCellStyleSubtitle, reuseIdentifier:CellID)
|
|
38
|
+
location = LocationsStore.shared.locations[indexPath.row]
|
|
39
|
+
|
|
40
|
+
@date_formatter ||= NSDateFormatter.alloc.init.tap do |df|
|
|
41
|
+
df.timeStyle = NSDateFormatterMediumStyle
|
|
42
|
+
df.dateStyle = NSDateFormatterMediumStyle
|
|
43
|
+
end
|
|
44
|
+
cell.textLabel.text = @date_formatter.stringFromDate(location.creation_date)
|
|
45
|
+
cell.detailTextLabel.text = "%0.3f, %0.3f" % [location.latitude, location.longitude]
|
|
46
|
+
cell
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def tableView(tableView, editingStyleForRowAtIndexPath:indexPath)
|
|
50
|
+
UITableViewCellEditingStyleDelete
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def tableView(tableView, commitEditingStyle:editingStyle, forRowAtIndexPath:indexPath)
|
|
54
|
+
location = LocationsStore.shared.locations[indexPath.row]
|
|
55
|
+
LocationsStore.shared.remove_location(location)
|
|
56
|
+
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation:UITableViewRowAnimationFade)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def locationManager(manager, didUpdateToLocation:newLocation, fromLocation:oldLocation)
|
|
60
|
+
navigationItem.rightBarButtonItem.enabled = true
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def locationManager(manager, didFailWithError:error)
|
|
64
|
+
navigationItem.rightBarButtonItem.enabled = false
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
class LocationsStore
|
|
2
|
+
def self.shared
|
|
3
|
+
# Our store is a singleton object.
|
|
4
|
+
@shared ||= LocationsStore.new
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def locations
|
|
8
|
+
@locations ||= begin
|
|
9
|
+
# Fetch all locations from the model, sorting by the creation date.
|
|
10
|
+
request = NSFetchRequest.alloc.init
|
|
11
|
+
request.entity = NSEntityDescription.entityForName('Location', inManagedObjectContext:@context)
|
|
12
|
+
request.sortDescriptors = [NSSortDescriptor.alloc.initWithKey('creation_date', ascending:false)]
|
|
13
|
+
|
|
14
|
+
error_ptr = Pointer.new(:object)
|
|
15
|
+
data = @context.executeFetchRequest(request, error:error_ptr)
|
|
16
|
+
if data == nil
|
|
17
|
+
raise "Error when fetching data: #{error_ptr[0].description}"
|
|
18
|
+
end
|
|
19
|
+
data
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def add_location
|
|
24
|
+
# Yield a blank, newly created Location entity, then save the model.
|
|
25
|
+
yield NSEntityDescription.insertNewObjectForEntityForName('Location', inManagedObjectContext:@context)
|
|
26
|
+
save
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def remove_location(location)
|
|
30
|
+
# Delete the given entity, then save the model.
|
|
31
|
+
@context.deleteObject(location)
|
|
32
|
+
save
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def initialize
|
|
38
|
+
# Create the model programmatically. Our model has only one entity, the Location class, and the data will be stored in a SQLite database, inside the application's Documents folder.
|
|
39
|
+
model = NSManagedObjectModel.alloc.init
|
|
40
|
+
model.entities = [Location.entity]
|
|
41
|
+
|
|
42
|
+
store = NSPersistentStoreCoordinator.alloc.initWithManagedObjectModel(model)
|
|
43
|
+
store_url = NSURL.fileURLWithPath(File.join(NSHomeDirectory(), 'Documents', 'Locations.sqlite'))
|
|
44
|
+
error_ptr = Pointer.new(:object)
|
|
45
|
+
unless store.addPersistentStoreWithType(NSSQLiteStoreType, configuration:nil, URL:store_url, options:nil, error:error_ptr)
|
|
46
|
+
raise "Can't add persistent SQLite store: #{error_ptr[0].description}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
context = NSManagedObjectContext.alloc.init
|
|
50
|
+
context.persistentStoreCoordinator = store
|
|
51
|
+
@context = context
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def save
|
|
55
|
+
error_ptr = Pointer.new(:object)
|
|
56
|
+
unless @context.save(error_ptr)
|
|
57
|
+
raise "Error when saving the model: #{error_ptr[0].description}"
|
|
58
|
+
end
|
|
59
|
+
@locations = nil
|
|
60
|
+
end
|
|
61
|
+
end
|
|
File without changes
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
$:.unshift("/Library/RubyMotion/lib")
|
|
2
|
+
require 'motion/project'
|
|
3
|
+
|
|
4
|
+
#ENV['debug'] ||= '0' # Because the REPL cannot be loaded with CoreImage yet.
|
|
5
|
+
|
|
6
|
+
Motion::Project::App.setup do |app|
|
|
7
|
+
# Use `rake config' to see complete project settings.
|
|
8
|
+
app.name = 'Mustache'
|
|
9
|
+
app.frameworks += ['QuartzCore', 'CoreImage']
|
|
10
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
class AppDelegate
|
|
2
|
+
def application(application, didFinishLaunchingWithOptions:launchOptions)
|
|
3
|
+
@window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
|
|
4
|
+
@window.rootViewController = MustacheViewController.alloc.init
|
|
5
|
+
@window.rootViewController.wantsFullScreenLayout = true
|
|
6
|
+
@window.makeKeyAndVisible
|
|
7
|
+
true
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
class MustacheViewController < UIViewController
|
|
2
|
+
def loadView
|
|
3
|
+
self.view = UIImageView.alloc.init
|
|
4
|
+
@debug_face = false # Set to true to debug face features.
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def viewDidLoad
|
|
8
|
+
@images = %w{matz guido kay jmccolor}.map { |name| UIImage.imageNamed(name + '.jpg') }
|
|
9
|
+
view.image = @images.first
|
|
10
|
+
view.contentMode = UIViewContentModeScaleAspectFit
|
|
11
|
+
view.userInteractionEnabled = true
|
|
12
|
+
|
|
13
|
+
previousGesture = UISwipeGestureRecognizer.alloc.initWithTarget(self, action:'swipePreviousGesture:')
|
|
14
|
+
previousGesture.direction = UISwipeGestureRecognizerDirectionLeft
|
|
15
|
+
view.addGestureRecognizer(previousGesture)
|
|
16
|
+
nextGesture = UISwipeGestureRecognizer.alloc.initWithTarget(self, action:'swipeNextGesture:')
|
|
17
|
+
nextGesture.direction = UISwipeGestureRecognizerDirectionRight
|
|
18
|
+
view.addGestureRecognizer(nextGesture)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def viewDidAppear(animated)
|
|
22
|
+
mustachify
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def mustachify
|
|
26
|
+
# Remove previous mustaches.
|
|
27
|
+
view.subviews.each { |v| v.removeFromSuperview }
|
|
28
|
+
|
|
29
|
+
# CoreImage used a coordinate system which is flipped on the Y axis
|
|
30
|
+
# compared to UIKit. Also, a UIImageView can return an image larger than
|
|
31
|
+
# itself. To properly translate points, we use an affine transform.
|
|
32
|
+
transform = CGAffineTransformMakeScale(view.bounds.size.width / view.image.size.width, -1 * (view.bounds.size.height / view.image.size.height))
|
|
33
|
+
transform = CGAffineTransformTranslate(transform, 0, -view.image.size.height)
|
|
34
|
+
|
|
35
|
+
image = CIImage.imageWithCGImage(view.image.CGImage)
|
|
36
|
+
@detector ||= CIDetector.detectorOfType CIDetectorTypeFace, context:nil, options: { CIDetectorAccuracy: CIDetectorAccuracyHigh }
|
|
37
|
+
@detector.featuresInImage(image).each do |feature|
|
|
38
|
+
# We need the mouth and eyes positions to determine where the mustache
|
|
39
|
+
# should be added.
|
|
40
|
+
next unless feature.hasMouthPosition and feature.hasLeftEyePosition and feature.hasRightEyePosition
|
|
41
|
+
|
|
42
|
+
if @debug_face
|
|
43
|
+
[feature.leftEyePosition,feature.rightEyePosition,feature.mouthPosition].each do |pt|
|
|
44
|
+
v = UIView.alloc.initWithFrame CGRectMake(0, 0, 20, 20)
|
|
45
|
+
v.backgroundColor = UIColor.greenColor.colorWithAlphaComponent(0.2)
|
|
46
|
+
pt = CGPointApplyAffineTransform(pt, transform)
|
|
47
|
+
v.center = pt
|
|
48
|
+
view.addSubview(v)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Create the mustache view.
|
|
53
|
+
mustacheView = UIImageView.alloc.init
|
|
54
|
+
mustacheView.image = UIImage.imageNamed('mustache')
|
|
55
|
+
mustacheView.contentMode = UIViewContentModeScaleAspectFit
|
|
56
|
+
|
|
57
|
+
# Compute its location and size, based on the position of the eyes and
|
|
58
|
+
# mouth.
|
|
59
|
+
w = feature.bounds.size.width
|
|
60
|
+
h = feature.bounds.size.height / 5
|
|
61
|
+
x = (feature.mouthPosition.x + (feature.leftEyePosition.x + feature.rightEyePosition.x) / 2) / 2 - w / 2
|
|
62
|
+
y = feature.mouthPosition.y
|
|
63
|
+
mustacheView.frame = CGRectApplyAffineTransform([[x, y], [w, h]], transform)
|
|
64
|
+
|
|
65
|
+
# Apply a rotation on the mustache, based on the face inclination.
|
|
66
|
+
mustacheAngle = Math.atan2(feature.leftEyePosition.x - feature.rightEyePosition.x, feature.leftEyePosition.y - feature.rightEyePosition.y) + Math::PI/2
|
|
67
|
+
mustacheView.transform = CGAffineTransformMakeRotation(mustacheAngle)
|
|
68
|
+
|
|
69
|
+
view.addSubview(mustacheView)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def shouldAutorotateToInterfaceOrientation(*)
|
|
74
|
+
mustachify
|
|
75
|
+
true
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def swipePreviousGesture(gesture)
|
|
79
|
+
idx = @images.index(view.image)
|
|
80
|
+
view.image =
|
|
81
|
+
if idx == 0
|
|
82
|
+
@images.last
|
|
83
|
+
else
|
|
84
|
+
@images[idx - 1]
|
|
85
|
+
end
|
|
86
|
+
mustachify
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def swipeNextGesture(gesture)
|
|
90
|
+
idx = @images.index(view.image)
|
|
91
|
+
view.image =
|
|
92
|
+
if idx == @images.size - 1
|
|
93
|
+
@images.first
|
|
94
|
+
else
|
|
95
|
+
@images[idx + 1]
|
|
96
|
+
end
|
|
97
|
+
mustachify
|
|
98
|
+
end
|
|
99
|
+
end
|
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
class AppDelegate
|
|
2
|
+
def application(application, didFinishLaunchingWithOptions:launchOptions)
|
|
3
|
+
@window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
|
|
4
|
+
@window.rootViewController = PaintViewController.alloc.init
|
|
5
|
+
@window.rootViewController.wantsFullScreenLayout = true
|
|
6
|
+
@window.makeKeyAndVisible
|
|
7
|
+
true
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
class PaintView < UIView
|
|
2
|
+
def initWithFrame(ect)
|
|
3
|
+
if super
|
|
4
|
+
path = NSBundle.mainBundle.pathForResource('erase', ofType:'caf')
|
|
5
|
+
url = NSURL.fileURLWithPath(path)
|
|
6
|
+
error_ptr = Pointer.new(:id)
|
|
7
|
+
@eraseSound = AVAudioPlayer.alloc.initWithContentsOfURL(url,
|
|
8
|
+
error:error_ptr)
|
|
9
|
+
unless @eraseSound
|
|
10
|
+
raise "Can't open sound file: #{error_ptr[0].description}"
|
|
11
|
+
end
|
|
12
|
+
@paths = []
|
|
13
|
+
end
|
|
14
|
+
self
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def drawRect(rect)
|
|
18
|
+
UIColor.blackColor.set
|
|
19
|
+
UIBezierPath.bezierPathWithRect(rect).fill
|
|
20
|
+
@paths.each do |path, color|
|
|
21
|
+
color.set
|
|
22
|
+
path.stroke
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def touchesBegan(touches, withEvent:event)
|
|
27
|
+
bp = UIBezierPath.alloc.init
|
|
28
|
+
bp.lineWidth = 3.0
|
|
29
|
+
random_color = begin
|
|
30
|
+
red, green, blue = rand(100)/100.0, rand(100)/100.0, rand(100)/100.0
|
|
31
|
+
UIColor.alloc.initWithRed(red/100.0, green:green, blue:blue, alpha:1.0)
|
|
32
|
+
end
|
|
33
|
+
@paths << [bp, random_color]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def touchesMoved(touches, withEvent:event)
|
|
37
|
+
touch = event.touchesForView(self).anyObject
|
|
38
|
+
point = touch.locationInView(self)
|
|
39
|
+
if @previousPoint and !@paths.empty?
|
|
40
|
+
bp = @paths.last.first
|
|
41
|
+
bp.moveToPoint(@previousPoint)
|
|
42
|
+
bp.addLineToPoint(point)
|
|
43
|
+
end
|
|
44
|
+
@previousPoint = point
|
|
45
|
+
setNeedsDisplay
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def touchesEnded(touches, withEvent:event)
|
|
49
|
+
@previousPoint = nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def eraseContent
|
|
53
|
+
@paths.clear
|
|
54
|
+
@eraseSound.play
|
|
55
|
+
setNeedsDisplay
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class PaintViewController < UIViewController
|
|
2
|
+
def loadView
|
|
3
|
+
self.view = PaintView.alloc.init
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def canBecomeFirstResponder
|
|
7
|
+
true
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def motionEnded(motion, withEvent:event)
|
|
11
|
+
self.view.eraseContent if motion == UIEventSubtypeMotionShake
|
|
12
|
+
end
|
|
13
|
+
end
|