├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── codemagic.yaml ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ └── contents.xcworkspacedata │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ ├── demos.dart │ ├── interactive_canvas.dart │ ├── interactive_examples.dart │ ├── main.dart │ └── pages │ │ ├── decoration_page.dart │ │ ├── home_page.dart │ │ └── interactive_example_page.dart ├── pubspec.lock └── pubspec.yaml ├── lib ├── rough.dart └── src │ ├── config.dart │ ├── core.dart │ ├── decoration.dart │ ├── entities.dart │ ├── filler.dart │ ├── generator.dart │ ├── geometry.dart │ ├── renderer.dart │ └── rough.dart ├── pubspec.lock ├── pubspec.yaml ├── screenshots ├── circle.png ├── example_app_1.jpg ├── example_app_2.jpg ├── example_app_3.jpg └── example_app_4.jpg ├── scripts └── cache.sh └── test └── rough_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | build/ 32 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Flutter.podspec 62 | **/ios/Flutter/Generated.xcconfig 63 | **/ios/Flutter/app.flx 64 | **/ios/Flutter/app.zip 65 | **/ios/Flutter/flutter_assets/ 66 | **/ios/Flutter/flutter_export_environment.sh 67 | **/ios/ServiceDefinitions.json 68 | **/ios/Runner/GeneratedPluginRegistrant.* 69 | 70 | # Exceptions to above rules. 71 | !**/ios/**/default.mode1v3 72 | !**/ios/**/default.mode2v3 73 | !**/ios/**/default.pbxuser 74 | !**/ios/**/default.perspectivev3 75 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 76 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: f139b11009aeb8ed2a3a3aa8b0066e482709dde3 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.1.1] - 2020-May-5. 2 | 3 | * `FillConfig no has a builder` 4 | * `Rough.draw` is now `drawRough`, a extension method of ``Draw 5 | * Basic external api documentation 6 | 7 | ## [0.1.0+2] - 2020-May-5. 8 | 9 | * Basic documentation 10 | * Cute badges! 11 | 12 | ## [0.1.0+1] - 2020-May-5. 13 | 14 | * Testing publication through CI 15 | 16 | ## [0.1.0] - 2020-May-4. 17 | 18 | * Initial release 19 | * support for basic figures with some fillings. 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Sergi Martínez López 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Pub](https://img.shields.io/pub/v/rough?label=latest%20version)](https://pub.dev/packages/rough) 2 | [![GitHub Release Date](https://img.shields.io/github/release-date/sergiandreplace/flutter_rough)](https://pub.dev/packages/rough) 3 | [![GitHub](https://img.shields.io/github/license/sergiandreplace/flutter_rough)](https://github.com/sergiandreplace/flutter_rough/blob/master/LICENSE) 4 | 5 | # Rough 6 | 7 | Rough is a library that allows you draw in a sketchy, hand-drawn-like style. It's a direct port of [Rough.js](https://roughjs.com/). 8 | 9 | ## Installation 10 | 11 | In the `dependencies:` section of your `pubspec.yaml`, add the following line: 12 | 13 | ```yaml 14 | dependencies: 15 | rough: 16 | ``` 17 | ## Basic usage 18 | 19 | Right now only drawing via canvas is supported. This is a basic documentation in case you want to play around with Rough. I can't ensure non-breaking changes of the library interface. 20 | 21 | To draw a figure you have to: 22 | 23 | 1. Create a `DrawConfig` object to determine how your drawing will look. 24 | 2. Create a `Filler` to be used when drawing objects (you have to provide a configuration for the filling and a `DrawConfig` for the filling path). 25 | 3. Create a `Generator` object using the created `DrawConfig` and `Filler`. This will define a drawing/filling style. 26 | 4. Invoke the drawing method from the `Generator` to create a `Drawable`. 27 | 5. Paint the `Drawable` in the canvas using the `drawRough` method extension for `Canvas`. 28 | 29 | Here an example on how to draw a circle: 30 | 31 | ```dart 32 | //Create a `DrawConfig` object. 33 | DrawConfig myDrawConfig = DrawConfig.build( 34 | roughness: 3, 35 | curveStepCount: 14, 36 | maxRandomnessOffset: 3, 37 | ); 38 | 39 | //Create a `Filler` with a configuration (we reuse the drawConfig in this case). 40 | FillerConfig myFillerConfig = FillerConfig( 41 | hachureGap: 8, 42 | hachureAngle: -20, 43 | drawConfig: myDrawConfig, 44 | ); 45 | Filler myFiller = ZigZagFiller(myFillerConfig); 46 | 47 | //Create a `Generator` with the created `DrawConfig` and `Filler` 48 | Generator generator = Generator( 49 | myDrawConfig, 50 | myFiller, 51 | ); 52 | 53 | //4. Build a circle `Drawable`. 54 | Drawable figure = generator.circle(200, 200, 320); 55 | 56 | //5. Paint the `Drawable` in the canvas. 57 | Canvas.drawRough(figure, pathPaint, fillPaint); 58 | ``` 59 | 60 | And this is the result: 61 | 62 | ![Result](https://raw.githubusercontent.com/sergiandreplace/flutter_rough/master/screenshots/circle.png) 63 | 64 | Both `DrawConfig` and `FillerConfig` will use default values for anything not specified. 65 | 66 | ## Samples 67 | 68 | Some screenshots of the example app: 69 | 70 | 71 | ![Example 1](https://raw.githubusercontent.com/sergiandreplace/flutter_rough/master/screenshots/example_app_1.jpg) 72 | ![Example 2](https://raw.githubusercontent.com/sergiandreplace/flutter_rough/master/screenshots/example_app_2.jpg) 73 | ![Example 3](https://raw.githubusercontent.com/sergiandreplace/flutter_rough/master/screenshots/example_app_3.jpg) 74 | ![Example 4](https://raw.githubusercontent.com/sergiandreplace/flutter_rough/master/screenshots/example_app_4.jpg) 75 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:extra_pedantic/analysis_options_flutter.yaml 2 | analyzer: 3 | strong-mode: 4 | implicit-casts: false 5 | implicit-dynamic: false 6 | linter: 7 | rules: 8 | - annotate_overrides 9 | - avoid_empty_else 10 | - avoid_function_literals_in_foreach_calls 11 | - avoid_init_to_null 12 | - avoid_null_checks_in_equality_operators 13 | - avoid_relative_lib_imports 14 | - avoid_renaming_method_parameters 15 | - avoid_return_types_on_setters 16 | - avoid_returning_null 17 | - avoid_types_as_parameter_names 18 | - avoid_unused_constructor_parameters 19 | - await_only_futures 20 | - camel_case_types 21 | - cancel_subscriptions 22 | - cascade_invocations 23 | - comment_references 24 | - constant_identifier_names 25 | - control_flow_in_finally 26 | - directives_ordering 27 | - empty_catches 28 | - empty_constructor_bodies 29 | - empty_statements 30 | - hash_and_equals 31 | - implementation_imports 32 | - invariant_booleans 33 | - iterable_contains_unrelated_type 34 | - library_names 35 | - library_prefixes 36 | - list_remove_unrelated_type 37 | - no_adjacent_strings_in_list 38 | - no_duplicate_case_values 39 | - non_constant_identifier_names 40 | - null_closures 41 | - only_throw_errors 42 | - overridden_fields 43 | - package_api_docs 44 | - package_names 45 | - package_prefixed_library_names 46 | - prefer_adjacent_string_concatenation 47 | - prefer_collection_literals 48 | - prefer_conditional_assignment 49 | - prefer_const_constructors 50 | - prefer_contains 51 | - prefer_equal_for_default_values 52 | - prefer_final_fields 53 | - prefer_initializing_formals 54 | - prefer_interpolation_to_compose_strings 55 | - prefer_generic_function_type_aliases 56 | - prefer_is_empty 57 | - prefer_is_not_empty 58 | - prefer_single_quotes 59 | - prefer_typing_uninitialized_variables 60 | # - public_member_api_docs 61 | - recursive_getters 62 | - slash_for_doc_comments 63 | - test_types_in_equals 64 | - throw_in_finally 65 | - type_init_formals 66 | - unawaited_futures 67 | - unnecessary_brace_in_string_interps 68 | - unnecessary_const 69 | - unnecessary_getters_setters 70 | - unnecessary_lambdas 71 | - unnecessary_new 72 | - unnecessary_null_aware_assignments 73 | - unnecessary_statements 74 | - unnecessary_this 75 | - unrelated_type_equality_checks 76 | - use_rethrow_when_possible 77 | - valid_regexps -------------------------------------------------------------------------------- /codemagic.yaml: -------------------------------------------------------------------------------- 1 | workflows: 2 | publish: 3 | name: Publish a new version 4 | environment: 5 | vars: 6 | CACHED_JSON: Encrypted(Z0FBQUFBQmVzSnQ3SVlzbmVNWnVJM1BlLWZpdkFweHNnaXg5eEtfNktjMUVUcnE1V1k4ZHkzUi0wR0hvcW1COUxwQjFpSGZRVWFwWWpxVnhOU0VqRDFVV2RvRENkeUtBUndRMThTRXRCSkxJRHFmY0NGejZGMWpWRWtma1U5dlU3ZFhpa3FLX0hESTF3YTRVMW1QYUJiUDh0QkRNb2lONmZnLUJScUZSalVXbENVQXdkQ0FVLVFJNEpfdjRtSl9MbmZSdEV4R0E1TXptU29rekg3YzZnMUt6dFRBR2pWcDY3ajkyazA5UDR3RGxUc2hjZkNSdXZVcmRwZmczWHZiY0JGWS1acmNkcXF4a1g4cUpMdk15Z2ozNThvdGVXN1M4SEhxZHNCd1ZpRk5fWXYzbzRuc2plUjNMSU9sUTFwdDMwcmlVYkZuOWR3OE9lbzNsdTEzV1RJYWVRNWZsemFLN2M0RWFMR25wZW92NmJaOHpYMkVIRkJOTlVSYi1tVVd5OEdVNkdqWnh2WElsQ2FLcEdwSUdiSDdQVUlzNURud3dnbGtDMWJSN3J6N2hxN2xfTzRjLXZrbThveDFIQ19JVHpjV0pKYU82VnlEbEhTbFdVc2NNNGZCZGVlbzJweUxvM2s1anZzek5VbVRDNHlNSUJqX0V3SFFEdEdPMzIwNjdZWHhCb0d3WEhiV1hPX25hQ3hFZzVCbWg3cG83U20wUXdETWhZZnZaRUxBdEd4Y2s5LTBPYXo2RXhDX2NOdkJMS3JlS25WUndnYTZCc2N0SmlpNHB3MFEtTzlEMjlBR1llZTJuME5lcUMxa2dqcFBvOTVaN2FmekdMakRHaG1lZ3pMUGkzY1ZnOW5TdnhwSW92NzZWOGkzVGtiUUFUcjFMV2VMR0R3dElRbDc3RGh5UEpQZjBIeS1zM0pwNXlLUlJfQnc1ajlOdzRFcUI=) 7 | flutter: stable 8 | cache: 9 | cache_paths: 10 | - ~/.pub-cache 11 | triggering: 12 | events: 13 | - tag 14 | branch_patterns: 15 | - pattern: 'test_codemagic' 16 | include: true 17 | source: true 18 | scripts: 19 | - flutter packages pub get 20 | - flutter format . 21 | - chmod +x ./scripts/cache.sh 22 | - ./scripts/cache.sh 23 | - flutter pub publish --dry-run 24 | - flutter pub publish -f 25 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: f139b11009aeb8ed2a3a3aa8b0066e482709dde3 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Rough example 2 | 3 | This in example application to showcase the use of Rough. -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 28 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "com.blindbugs.roughgallery" 42 | minSdkVersion 16 43 | targetSdkVersion 28 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 47 | } 48 | 49 | buildTypes { 50 | release { 51 | // TODO: Add your own signing config for the release build. 52 | // Signing with the debug keys for now, so `flutter run --release` works. 53 | signingConfig signingConfigs.debug 54 | } 55 | } 56 | } 57 | 58 | flutter { 59 | source '../..' 60 | } 61 | 62 | dependencies { 63 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 64 | testImplementation 'junit:junit:4.12' 65 | androidTestImplementation 'androidx.test:runner:1.1.1' 66 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 67 | } 68 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 12 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.example 2 | 3 | import androidx.annotation.NonNull; 4 | import io.flutter.embedding.android.FlutterActivity 5 | import io.flutter.embedding.engine.FlutterEngine 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity: FlutterActivity() { 9 | override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { 10 | GeneratedPluginRegistrant.registerWith(flutterEngine); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiandreplace/flutter_rough/4ed0da47b52cb4d3aa7c38d088f3f4c5a19d4aeb/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiandreplace/flutter_rough/4ed0da47b52cb4d3aa7c38d088f3f4c5a19d4aeb/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiandreplace/flutter_rough/4ed0da47b52cb4d3aa7c38d088f3f4c5a19d4aeb/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiandreplace/flutter_rough/4ed0da47b52cb4d3aa7c38d088f3f4c5a19d4aeb/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiandreplace/flutter_rough/4ed0da47b52cb4d3aa7c38d088f3f4c5a19d4aeb/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 13 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 14 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 15 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 16 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 17 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 18 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 19 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXCopyFilesBuildPhase section */ 23 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 24 | isa = PBXCopyFilesBuildPhase; 25 | buildActionMask = 2147483647; 26 | dstPath = ""; 27 | dstSubfolderSpec = 10; 28 | files = ( 29 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, 30 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, 31 | ); 32 | name = "Embed Frameworks"; 33 | runOnlyForDeploymentPostprocessing = 0; 34 | }; 35 | /* End PBXCopyFilesBuildPhase section */ 36 | 37 | /* Begin PBXFileReference section */ 38 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 39 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 40 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 41 | 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 42 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 43 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 44 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 45 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 46 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 47 | 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 48 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 49 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 50 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 51 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 52 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 53 | /* End PBXFileReference section */ 54 | 55 | /* Begin PBXFrameworksBuildPhase section */ 56 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 57 | isa = PBXFrameworksBuildPhase; 58 | buildActionMask = 2147483647; 59 | files = ( 60 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 61 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 62 | ); 63 | runOnlyForDeploymentPostprocessing = 0; 64 | }; 65 | /* End PBXFrameworksBuildPhase section */ 66 | 67 | /* Begin PBXGroup section */ 68 | 9740EEB11CF90186004384FC /* Flutter */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | 3B80C3931E831B6300D905FE /* App.framework */, 72 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 73 | 9740EEBA1CF902C7004384FC /* Flutter.framework */, 74 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 75 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 76 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 77 | ); 78 | name = Flutter; 79 | sourceTree = ""; 80 | }; 81 | 97C146E51CF9000F007C117D = { 82 | isa = PBXGroup; 83 | children = ( 84 | 9740EEB11CF90186004384FC /* Flutter */, 85 | 97C146F01CF9000F007C117D /* Runner */, 86 | 97C146EF1CF9000F007C117D /* Products */, 87 | ); 88 | sourceTree = ""; 89 | }; 90 | 97C146EF1CF9000F007C117D /* Products */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | 97C146EE1CF9000F007C117D /* Runner.app */, 94 | ); 95 | name = Products; 96 | sourceTree = ""; 97 | }; 98 | 97C146F01CF9000F007C117D /* Runner */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 102 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 103 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 104 | 97C147021CF9000F007C117D /* Info.plist */, 105 | 97C146F11CF9000F007C117D /* Supporting Files */, 106 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 107 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 108 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 109 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 110 | ); 111 | path = Runner; 112 | sourceTree = ""; 113 | }; 114 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | ); 118 | name = "Supporting Files"; 119 | sourceTree = ""; 120 | }; 121 | /* End PBXGroup section */ 122 | 123 | /* Begin PBXNativeTarget section */ 124 | 97C146ED1CF9000F007C117D /* Runner */ = { 125 | isa = PBXNativeTarget; 126 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 127 | buildPhases = ( 128 | 9740EEB61CF901F6004384FC /* Run Script */, 129 | 97C146EA1CF9000F007C117D /* Sources */, 130 | 97C146EB1CF9000F007C117D /* Frameworks */, 131 | 97C146EC1CF9000F007C117D /* Resources */, 132 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 133 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 134 | ); 135 | buildRules = ( 136 | ); 137 | dependencies = ( 138 | ); 139 | name = Runner; 140 | productName = Runner; 141 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 142 | productType = "com.apple.product-type.application"; 143 | }; 144 | /* End PBXNativeTarget section */ 145 | 146 | /* Begin PBXProject section */ 147 | 97C146E61CF9000F007C117D /* Project object */ = { 148 | isa = PBXProject; 149 | attributes = { 150 | LastUpgradeCheck = 1020; 151 | ORGANIZATIONNAME = "The Chromium Authors"; 152 | TargetAttributes = { 153 | 97C146ED1CF9000F007C117D = { 154 | CreatedOnToolsVersion = 7.3.1; 155 | LastSwiftMigration = 1100; 156 | }; 157 | }; 158 | }; 159 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 160 | compatibilityVersion = "Xcode 3.2"; 161 | developmentRegion = en; 162 | hasScannedForEncodings = 0; 163 | knownRegions = ( 164 | en, 165 | Base, 166 | ); 167 | mainGroup = 97C146E51CF9000F007C117D; 168 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 169 | projectDirPath = ""; 170 | projectRoot = ""; 171 | targets = ( 172 | 97C146ED1CF9000F007C117D /* Runner */, 173 | ); 174 | }; 175 | /* End PBXProject section */ 176 | 177 | /* Begin PBXResourcesBuildPhase section */ 178 | 97C146EC1CF9000F007C117D /* Resources */ = { 179 | isa = PBXResourcesBuildPhase; 180 | buildActionMask = 2147483647; 181 | files = ( 182 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 183 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 184 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 185 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 186 | ); 187 | runOnlyForDeploymentPostprocessing = 0; 188 | }; 189 | /* End PBXResourcesBuildPhase section */ 190 | 191 | /* Begin PBXShellScriptBuildPhase section */ 192 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 193 | isa = PBXShellScriptBuildPhase; 194 | buildActionMask = 2147483647; 195 | files = ( 196 | ); 197 | inputPaths = ( 198 | ); 199 | name = "Thin Binary"; 200 | outputPaths = ( 201 | ); 202 | runOnlyForDeploymentPostprocessing = 0; 203 | shellPath = /bin/sh; 204 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; 205 | }; 206 | 9740EEB61CF901F6004384FC /* Run Script */ = { 207 | isa = PBXShellScriptBuildPhase; 208 | buildActionMask = 2147483647; 209 | files = ( 210 | ); 211 | inputPaths = ( 212 | ); 213 | name = "Run Script"; 214 | outputPaths = ( 215 | ); 216 | runOnlyForDeploymentPostprocessing = 0; 217 | shellPath = /bin/sh; 218 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 219 | }; 220 | /* End PBXShellScriptBuildPhase section */ 221 | 222 | /* Begin PBXSourcesBuildPhase section */ 223 | 97C146EA1CF9000F007C117D /* Sources */ = { 224 | isa = PBXSourcesBuildPhase; 225 | buildActionMask = 2147483647; 226 | files = ( 227 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 228 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 229 | ); 230 | runOnlyForDeploymentPostprocessing = 0; 231 | }; 232 | /* End PBXSourcesBuildPhase section */ 233 | 234 | /* Begin PBXVariantGroup section */ 235 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 236 | isa = PBXVariantGroup; 237 | children = ( 238 | 97C146FB1CF9000F007C117D /* Base */, 239 | ); 240 | name = Main.storyboard; 241 | sourceTree = ""; 242 | }; 243 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 244 | isa = PBXVariantGroup; 245 | children = ( 246 | 97C147001CF9000F007C117D /* Base */, 247 | ); 248 | name = LaunchScreen.storyboard; 249 | sourceTree = ""; 250 | }; 251 | /* End PBXVariantGroup section */ 252 | 253 | /* Begin XCBuildConfiguration section */ 254 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 255 | isa = XCBuildConfiguration; 256 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 257 | buildSettings = { 258 | ALWAYS_SEARCH_USER_PATHS = NO; 259 | CLANG_ANALYZER_NONNULL = YES; 260 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 261 | CLANG_CXX_LIBRARY = "libc++"; 262 | CLANG_ENABLE_MODULES = YES; 263 | CLANG_ENABLE_OBJC_ARC = YES; 264 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 265 | CLANG_WARN_BOOL_CONVERSION = YES; 266 | CLANG_WARN_COMMA = YES; 267 | CLANG_WARN_CONSTANT_CONVERSION = YES; 268 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 269 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 270 | CLANG_WARN_EMPTY_BODY = YES; 271 | CLANG_WARN_ENUM_CONVERSION = YES; 272 | CLANG_WARN_INFINITE_RECURSION = YES; 273 | CLANG_WARN_INT_CONVERSION = YES; 274 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 275 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 276 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 277 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 278 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 279 | CLANG_WARN_STRICT_PROTOTYPES = YES; 280 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 281 | CLANG_WARN_UNREACHABLE_CODE = YES; 282 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 283 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 284 | COPY_PHASE_STRIP = NO; 285 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 286 | ENABLE_NS_ASSERTIONS = NO; 287 | ENABLE_STRICT_OBJC_MSGSEND = YES; 288 | GCC_C_LANGUAGE_STANDARD = gnu99; 289 | GCC_NO_COMMON_BLOCKS = YES; 290 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 291 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 292 | GCC_WARN_UNDECLARED_SELECTOR = YES; 293 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 294 | GCC_WARN_UNUSED_FUNCTION = YES; 295 | GCC_WARN_UNUSED_VARIABLE = YES; 296 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 297 | MTL_ENABLE_DEBUG_INFO = NO; 298 | SDKROOT = iphoneos; 299 | SUPPORTED_PLATFORMS = iphoneos; 300 | TARGETED_DEVICE_FAMILY = "1,2"; 301 | VALIDATE_PRODUCT = YES; 302 | }; 303 | name = Profile; 304 | }; 305 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 306 | isa = XCBuildConfiguration; 307 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 308 | buildSettings = { 309 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 310 | CLANG_ENABLE_MODULES = YES; 311 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 312 | ENABLE_BITCODE = NO; 313 | FRAMEWORK_SEARCH_PATHS = ( 314 | "$(inherited)", 315 | "$(PROJECT_DIR)/Flutter", 316 | ); 317 | INFOPLIST_FILE = Runner/Info.plist; 318 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 319 | LIBRARY_SEARCH_PATHS = ( 320 | "$(inherited)", 321 | "$(PROJECT_DIR)/Flutter", 322 | ); 323 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 324 | PRODUCT_NAME = "$(TARGET_NAME)"; 325 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 326 | SWIFT_VERSION = 5.0; 327 | VERSIONING_SYSTEM = "apple-generic"; 328 | }; 329 | name = Profile; 330 | }; 331 | 97C147031CF9000F007C117D /* Debug */ = { 332 | isa = XCBuildConfiguration; 333 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 334 | buildSettings = { 335 | ALWAYS_SEARCH_USER_PATHS = NO; 336 | CLANG_ANALYZER_NONNULL = YES; 337 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 338 | CLANG_CXX_LIBRARY = "libc++"; 339 | CLANG_ENABLE_MODULES = YES; 340 | CLANG_ENABLE_OBJC_ARC = YES; 341 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 342 | CLANG_WARN_BOOL_CONVERSION = YES; 343 | CLANG_WARN_COMMA = YES; 344 | CLANG_WARN_CONSTANT_CONVERSION = YES; 345 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 346 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 347 | CLANG_WARN_EMPTY_BODY = YES; 348 | CLANG_WARN_ENUM_CONVERSION = YES; 349 | CLANG_WARN_INFINITE_RECURSION = YES; 350 | CLANG_WARN_INT_CONVERSION = YES; 351 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 352 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 353 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 354 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 355 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 356 | CLANG_WARN_STRICT_PROTOTYPES = YES; 357 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 358 | CLANG_WARN_UNREACHABLE_CODE = YES; 359 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 360 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 361 | COPY_PHASE_STRIP = NO; 362 | DEBUG_INFORMATION_FORMAT = dwarf; 363 | ENABLE_STRICT_OBJC_MSGSEND = YES; 364 | ENABLE_TESTABILITY = YES; 365 | GCC_C_LANGUAGE_STANDARD = gnu99; 366 | GCC_DYNAMIC_NO_PIC = NO; 367 | GCC_NO_COMMON_BLOCKS = YES; 368 | GCC_OPTIMIZATION_LEVEL = 0; 369 | GCC_PREPROCESSOR_DEFINITIONS = ( 370 | "DEBUG=1", 371 | "$(inherited)", 372 | ); 373 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 374 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 375 | GCC_WARN_UNDECLARED_SELECTOR = YES; 376 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 377 | GCC_WARN_UNUSED_FUNCTION = YES; 378 | GCC_WARN_UNUSED_VARIABLE = YES; 379 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 380 | MTL_ENABLE_DEBUG_INFO = YES; 381 | ONLY_ACTIVE_ARCH = YES; 382 | SDKROOT = iphoneos; 383 | TARGETED_DEVICE_FAMILY = "1,2"; 384 | }; 385 | name = Debug; 386 | }; 387 | 97C147041CF9000F007C117D /* Release */ = { 388 | isa = XCBuildConfiguration; 389 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 390 | buildSettings = { 391 | ALWAYS_SEARCH_USER_PATHS = NO; 392 | CLANG_ANALYZER_NONNULL = YES; 393 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 394 | CLANG_CXX_LIBRARY = "libc++"; 395 | CLANG_ENABLE_MODULES = YES; 396 | CLANG_ENABLE_OBJC_ARC = YES; 397 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 398 | CLANG_WARN_BOOL_CONVERSION = YES; 399 | CLANG_WARN_COMMA = YES; 400 | CLANG_WARN_CONSTANT_CONVERSION = YES; 401 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 402 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 403 | CLANG_WARN_EMPTY_BODY = YES; 404 | CLANG_WARN_ENUM_CONVERSION = YES; 405 | CLANG_WARN_INFINITE_RECURSION = YES; 406 | CLANG_WARN_INT_CONVERSION = YES; 407 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 408 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 409 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 410 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 411 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 412 | CLANG_WARN_STRICT_PROTOTYPES = YES; 413 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 414 | CLANG_WARN_UNREACHABLE_CODE = YES; 415 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 416 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 417 | COPY_PHASE_STRIP = NO; 418 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 419 | ENABLE_NS_ASSERTIONS = NO; 420 | ENABLE_STRICT_OBJC_MSGSEND = YES; 421 | GCC_C_LANGUAGE_STANDARD = gnu99; 422 | GCC_NO_COMMON_BLOCKS = YES; 423 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 424 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 425 | GCC_WARN_UNDECLARED_SELECTOR = YES; 426 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 427 | GCC_WARN_UNUSED_FUNCTION = YES; 428 | GCC_WARN_UNUSED_VARIABLE = YES; 429 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 430 | MTL_ENABLE_DEBUG_INFO = NO; 431 | SDKROOT = iphoneos; 432 | SUPPORTED_PLATFORMS = iphoneos; 433 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 434 | TARGETED_DEVICE_FAMILY = "1,2"; 435 | VALIDATE_PRODUCT = YES; 436 | }; 437 | name = Release; 438 | }; 439 | 97C147061CF9000F007C117D /* Debug */ = { 440 | isa = XCBuildConfiguration; 441 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 442 | buildSettings = { 443 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 444 | CLANG_ENABLE_MODULES = YES; 445 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 446 | ENABLE_BITCODE = NO; 447 | FRAMEWORK_SEARCH_PATHS = ( 448 | "$(inherited)", 449 | "$(PROJECT_DIR)/Flutter", 450 | ); 451 | INFOPLIST_FILE = Runner/Info.plist; 452 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 453 | LIBRARY_SEARCH_PATHS = ( 454 | "$(inherited)", 455 | "$(PROJECT_DIR)/Flutter", 456 | ); 457 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 458 | PRODUCT_NAME = "$(TARGET_NAME)"; 459 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 460 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 461 | SWIFT_VERSION = 5.0; 462 | VERSIONING_SYSTEM = "apple-generic"; 463 | }; 464 | name = Debug; 465 | }; 466 | 97C147071CF9000F007C117D /* Release */ = { 467 | isa = XCBuildConfiguration; 468 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 469 | buildSettings = { 470 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 471 | CLANG_ENABLE_MODULES = YES; 472 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 473 | ENABLE_BITCODE = NO; 474 | FRAMEWORK_SEARCH_PATHS = ( 475 | "$(inherited)", 476 | "$(PROJECT_DIR)/Flutter", 477 | ); 478 | INFOPLIST_FILE = Runner/Info.plist; 479 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 480 | LIBRARY_SEARCH_PATHS = ( 481 | "$(inherited)", 482 | "$(PROJECT_DIR)/Flutter", 483 | ); 484 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 485 | PRODUCT_NAME = "$(TARGET_NAME)"; 486 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 487 | SWIFT_VERSION = 5.0; 488 | VERSIONING_SYSTEM = "apple-generic"; 489 | }; 490 | name = Release; 491 | }; 492 | /* End XCBuildConfiguration section */ 493 | 494 | /* Begin XCConfigurationList section */ 495 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 496 | isa = XCConfigurationList; 497 | buildConfigurations = ( 498 | 97C147031CF9000F007C117D /* Debug */, 499 | 97C147041CF9000F007C117D /* Release */, 500 | 249021D3217E4FDB00AE95B9 /* Profile */, 501 | ); 502 | defaultConfigurationIsVisible = 0; 503 | defaultConfigurationName = Release; 504 | }; 505 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 506 | isa = XCConfigurationList; 507 | buildConfigurations = ( 508 | 97C147061CF9000F007C117D /* Debug */, 509 | 97C147071CF9000F007C117D /* Release */, 510 | 249021D4217E4FDB00AE95B9 /* Profile */, 511 | ); 512 | defaultConfigurationIsVisible = 0; 513 | defaultConfigurationName = Release; 514 | }; 515 | /* End XCConfigurationList section */ 516 | }; 517 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 518 | } 519 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiandreplace/flutter_rough/4ed0da47b52cb4d3aa7c38d088f3f4c5a19d4aeb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiandreplace/flutter_rough/4ed0da47b52cb4d3aa7c38d088f3f4c5a19d4aeb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiandreplace/flutter_rough/4ed0da47b52cb4d3aa7c38d088f3f4c5a19d4aeb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiandreplace/flutter_rough/4ed0da47b52cb4d3aa7c38d088f3f4c5a19d4aeb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiandreplace/flutter_rough/4ed0da47b52cb4d3aa7c38d088f3f4c5a19d4aeb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiandreplace/flutter_rough/4ed0da47b52cb4d3aa7c38d088f3f4c5a19d4aeb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiandreplace/flutter_rough/4ed0da47b52cb4d3aa7c38d088f3f4c5a19d4aeb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiandreplace/flutter_rough/4ed0da47b52cb4d3aa7c38d088f3f4c5a19d4aeb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiandreplace/flutter_rough/4ed0da47b52cb4d3aa7c38d088f3f4c5a19d4aeb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiandreplace/flutter_rough/4ed0da47b52cb4d3aa7c38d088f3f4c5a19d4aeb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiandreplace/flutter_rough/4ed0da47b52cb4d3aa7c38d088f3f4c5a19d4aeb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiandreplace/flutter_rough/4ed0da47b52cb4d3aa7c38d088f3f4c5a19d4aeb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiandreplace/flutter_rough/4ed0da47b52cb4d3aa7c38d088f3f4c5a19d4aeb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiandreplace/flutter_rough/4ed0da47b52cb4d3aa7c38d088f3f4c5a19d4aeb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiandreplace/flutter_rough/4ed0da47b52cb4d3aa7c38d088f3f4c5a19d4aeb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiandreplace/flutter_rough/4ed0da47b52cb4d3aa7c38d088f3f4c5a19d4aeb/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiandreplace/flutter_rough/4ed0da47b52cb4d3aa7c38d088f3f4c5a19d4aeb/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiandreplace/flutter_rough/4ed0da47b52cb4d3aa7c38d088f3f4c5a19d4aeb/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /example/lib/demos.dart: -------------------------------------------------------------------------------- 1 | import 'package:RoughExample/interactive_canvas.dart'; 2 | import 'package:RoughExample/pages/decoration_page.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | import 'interactive_examples.dart'; 6 | import 'pages/interactive_example_page.dart'; 7 | 8 | abstract class Demo { 9 | final String name; 10 | final String description; 11 | final Widget icon; 12 | 13 | Demo(this.name, this.description, this.icon); 14 | 15 | Widget buildPage(BuildContext context); 16 | } 17 | 18 | class InteractiveDemo extends Demo { 19 | final ExampleBuilder exampleBuilder; 20 | 21 | InteractiveDemo(String name, String description, this.exampleBuilder, Widget icon) : super(name, description, icon); 22 | 23 | @override 24 | Widget buildPage(BuildContext context) { 25 | return ExamplePage(title: name, exampleBuilder: exampleBuilder); 26 | } 27 | } 28 | 29 | class NormalDemo extends Demo { 30 | final WidgetBuilder builder; 31 | NormalDemo(String name, String description, this.builder, Widget icon) : super(name, description, icon); 32 | 33 | @override 34 | Widget buildPage(BuildContext context) { 35 | return builder(context); 36 | } 37 | } 38 | 39 | typedef ExampleBuilder = InteractiveExample Function(); 40 | 41 | final List demos = [ 42 | InteractiveDemo('Flutter logo', 'A simple Flutter logo drawn using Rough', () => FlutterLogoExample(), const FlutterLogo()), 43 | InteractiveDemo( 44 | 'Interactive circle', 45 | 'A circle drawn with Rough generated with interactive parameters', 46 | () => CircleExample(), 47 | const Icon(Icons.add_circle, size: 36), 48 | ), 49 | InteractiveDemo( 50 | 'Interactive rectangle', 51 | 'A rectange drawn with Rough generated with interactive parameters', 52 | () => RectangleExample(), 53 | const Icon(Icons.add_box, size: 36), 54 | ), 55 | InteractiveDemo( 56 | 'Interactive arc', 57 | 'An arc drawn with Rough generated with interactive parameters', 58 | () => ArcExample(), 59 | const Icon(Icons.pie_chart_outlined, size: 36), 60 | ), 61 | InteractiveDemo( 62 | 'Interactive curve', 63 | 'An curve drawn with Rough generated with interactive parameters', 64 | () => CurveExample(), 65 | const Icon(Icons.gesture, size: 36), 66 | ), 67 | NormalDemo( 68 | 'Decoration demo', 69 | 'Create decorations with Rough', 70 | (_) => DecorationExamplePage(), 71 | const Icon(Icons.format_shapes, size: 36), 72 | ) 73 | ]; 74 | -------------------------------------------------------------------------------- /example/lib/interactive_canvas.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:rough/rough.dart'; 6 | 7 | class DiscreteProperty { 8 | final String name; 9 | final double max; 10 | final double min; 11 | final int steps; 12 | final double value; 13 | 14 | DiscreteProperty({this.name, this.min, this.max, this.steps, this.value}); 15 | 16 | static List drawConfigProperties = [ 17 | DiscreteProperty(name: 'seed', min: 0, max: 50, steps: 50), 18 | DiscreteProperty(name: 'roughness', min: 0, max: 5, steps: 50), 19 | DiscreteProperty(name: 'curveFitting', min: 0, max: 5, steps: 50), 20 | DiscreteProperty(name: 'curveTightness', min: 0, max: 1, steps: 100), 21 | DiscreteProperty(name: 'curveStepCount', min: 1, max: 20, steps: 190), 22 | DiscreteProperty(name: 'bowing', min: 0, max: 20, steps: 400), 23 | DiscreteProperty(name: 'maxRandomnessOffset', min: 0, max: 20, steps: 50), 24 | ]; 25 | 26 | static List fillerConfigProperties = [ 27 | DiscreteProperty(name: 'fillWeight', min: 0, max: 50, steps: 500), 28 | DiscreteProperty(name: 'hachureAngle', min: 0, max: 360, steps: 360), 29 | DiscreteProperty(name: 'hachureGap', min: 0, max: 50, steps: 500), 30 | DiscreteProperty(name: 'dashOffset', min: 0, max: 50, steps: 500), 31 | DiscreteProperty(name: 'dashGap', min: 0, max: 50, steps: 500), 32 | DiscreteProperty(name: 'zigzagOffset', min: 0, max: 50, steps: 500), 33 | ]; 34 | } 35 | 36 | Map _fillers = { 37 | 'NoFiller': (fillerConfig) => NoFiller(fillerConfig), 38 | 'HachureFiller': (fillerConfig) => HachureFiller(fillerConfig), 39 | 'ZigZagFiller': (fillerConfig) => ZigZagFiller(fillerConfig), 40 | 'HatchFiller': (fillerConfig) => HatchFiller(fillerConfig), 41 | 'DotFiller': (fillerConfig) => DotFiller(fillerConfig), 42 | 'DashedFiller': (fillerConfig) => DashedFiller(fillerConfig), 43 | 'SolidFiller': (fillerConfig) => SolidFiller(fillerConfig), 44 | }; 45 | 46 | typedef PainterBuilder = InteractivePainter Function(DrawConfig); 47 | 48 | class InteractiveExamplePage extends StatelessWidget { 49 | final String title; 50 | final InteractiveExample example; 51 | 52 | const InteractiveExamplePage({Key key, this.title, this.example}) : super(key: key); 53 | 54 | @override 55 | Widget build(BuildContext context) { 56 | return Scaffold( 57 | appBar: AppBar(title: Text(title)), 58 | body: InteractiveBody( 59 | example: example, 60 | ), 61 | ); 62 | } 63 | } 64 | 65 | class InteractiveBody extends StatefulWidget { 66 | final InteractiveExample example; 67 | 68 | const InteractiveBody({Key key, this.example}) : super(key: key); 69 | 70 | @override 71 | _InteractiveBodyState createState() => _InteractiveBodyState(); 72 | } 73 | 74 | class _InteractiveBodyState extends State with TickerProviderStateMixin { 75 | Map drawConfigValues = HashMap(); 76 | Map fillerConfigValues = HashMap(); 77 | String fillerType; 78 | TabController _tabController; 79 | 80 | @override 81 | void initState() { 82 | super.initState(); 83 | drawConfigValues['maxRandomnessOffset'] = DrawConfig.defaultValues.roughness; 84 | drawConfigValues['bowing'] = DrawConfig.defaultValues.roughness; 85 | drawConfigValues['roughness'] = DrawConfig.defaultValues.roughness; 86 | drawConfigValues['curveFitting'] = DrawConfig.defaultValues.curveFitting; 87 | drawConfigValues['curveTightness'] = DrawConfig.defaultValues.curveTightness; 88 | drawConfigValues['curveStepCount'] = DrawConfig.defaultValues.curveStepCount; 89 | drawConfigValues['seed'] = DrawConfig.defaultValues.seed.toDouble(); 90 | fillerConfigValues['fillWeight'] = FillerConfig.defaultConfig.fillWeight; 91 | fillerConfigValues['hachureAngle'] = FillerConfig.defaultConfig.hachureAngle; 92 | fillerConfigValues['hachureGap'] = FillerConfig.defaultConfig.hachureGap; 93 | fillerConfigValues['dashOffset'] = FillerConfig.defaultConfig.dashOffset; 94 | fillerConfigValues['dashGap'] = FillerConfig.defaultConfig.dashGap; 95 | fillerConfigValues['zigzagOffset'] = FillerConfig.defaultConfig.zigzagOffset; 96 | fillerType = _fillers.keys.elementAt(0); 97 | _tabController = TabController( 98 | length: 2, 99 | initialIndex: 0, 100 | vsync: this, 101 | ); 102 | } 103 | 104 | void updateDrawingConfig({ 105 | String property, 106 | double value, 107 | }) { 108 | setState(() { 109 | drawConfigValues[property] = value; 110 | }); 111 | } 112 | 113 | void updateFillerConfig({ 114 | String property, 115 | double value, 116 | }) { 117 | setState(() { 118 | fillerConfigValues[property] = value; 119 | }); 120 | } 121 | 122 | void updateFillerType({String value}) { 123 | setState(() { 124 | fillerType = value; 125 | }); 126 | } 127 | 128 | @override 129 | Widget build(BuildContext context) { 130 | return Column( 131 | mainAxisSize: MainAxisSize.max, 132 | crossAxisAlignment: CrossAxisAlignment.stretch, 133 | children: [ 134 | Expanded( 135 | child: Card( 136 | child: InteractiveCanvas( 137 | example: widget.example, 138 | drawConfigValues: drawConfigValues, 139 | fillerConfigValues: fillerConfigValues, 140 | fillerType: fillerType, 141 | ), 142 | ), 143 | ), 144 | TabBar( 145 | controller: _tabController, 146 | tabs: [ 147 | ConfigTab(label: 'Draw', iconData: Icons.border_color), 148 | ConfigTab(label: 'Filler', iconData: Icons.format_color_fill), 149 | ], 150 | onTap: (index) => setState(() => _tabController.index = index), 151 | ), 152 | Container( 153 | height: 200, 154 | child: IndexedStack( 155 | sizing: StackFit.expand, 156 | index: _tabController.index, 157 | children: [ 158 | ListView( 159 | children: DiscreteProperty.drawConfigProperties 160 | .map( 161 | (property) => PropertySlider( 162 | label: property.name, 163 | value: drawConfigValues[property.name], 164 | min: property.min, 165 | max: property.max, 166 | steps: property.steps, 167 | onChange: (value) => updateDrawingConfig(property: property.name, value: value), 168 | ), 169 | ) 170 | .toList(), 171 | ), 172 | ListView( 173 | children: [ 174 | Padding( 175 | padding: const EdgeInsets.symmetric(horizontal: 16.0), 176 | child: DropdownButton( 177 | value: fillerType, 178 | isExpanded: false, 179 | onChanged: (value) { 180 | updateFillerType(value: value); 181 | }, 182 | underline: Container(), 183 | items: _fillers.keys 184 | .map((fillerKey) => DropdownMenuItem( 185 | value: fillerKey, 186 | child: Text(fillerKey), 187 | )) 188 | .toList(), 189 | ), 190 | ), 191 | ...DiscreteProperty.fillerConfigProperties 192 | .map( 193 | (property) => PropertySlider( 194 | label: property.name, 195 | value: fillerConfigValues[property.name], 196 | min: property.min, 197 | max: property.max, 198 | steps: property.steps, 199 | onChange: (value) => updateFillerConfig(property: property.name, value: value), 200 | ), 201 | ) 202 | .toList() 203 | ], 204 | ), 205 | ], 206 | ), 207 | ) 208 | ], 209 | ); 210 | } 211 | } 212 | 213 | class ConfigTab extends StatelessWidget { 214 | final String label; 215 | final IconData iconData; 216 | 217 | const ConfigTab({Key key, this.label, this.iconData}) : super(key: key); 218 | 219 | @override 220 | Widget build(BuildContext context) { 221 | return Tab( 222 | child: Row( 223 | mainAxisAlignment: MainAxisAlignment.center, 224 | children: [ 225 | Icon(iconData, size: 16), 226 | const SizedBox(width: 8), 227 | Text(label), 228 | ], 229 | ), 230 | ); 231 | } 232 | } 233 | 234 | class PropertySlider extends StatefulWidget { 235 | final String label; 236 | final double min; 237 | final double max; 238 | final int steps; 239 | final OnConfigChange onChange; 240 | final double value; 241 | 242 | const PropertySlider({Key key, this.value, this.label, this.min = 0, this.max = 0, this.steps = 10, this.onChange}) : super(key: key); 243 | 244 | @override 245 | _PropertySliderState createState() => _PropertySliderState(); 246 | } 247 | 248 | class _PropertySliderState extends State { 249 | double configValue; 250 | 251 | @override 252 | void initState() { 253 | super.initState(); 254 | configValue = widget.value; 255 | } 256 | 257 | void onConfigValueChange(double value) { 258 | if (configValue != value) { 259 | setState(() { 260 | configValue = value; 261 | }); 262 | widget.onChange(value); 263 | } 264 | } 265 | 266 | @override 267 | Widget build(BuildContext context) { 268 | return Row( 269 | children: [ 270 | Padding( 271 | padding: const EdgeInsets.only(left: 16.0), 272 | child: Text('${widget.label}: ${configValue.toStringAsFixed(1)}'), 273 | ), 274 | Expanded( 275 | child: Slider( 276 | value: configValue, 277 | divisions: widget.steps, 278 | min: widget.min, 279 | max: widget.max, 280 | onChanged: onConfigValueChange, 281 | ), 282 | ), 283 | ], 284 | ); 285 | } 286 | } 287 | 288 | typedef OnConfigChange = void Function(double); 289 | 290 | class InteractiveCanvas extends StatelessWidget { 291 | final InteractiveExample example; 292 | final Map drawConfigValues; 293 | final Map fillerConfigValues; 294 | final String fillerType; 295 | 296 | const InteractiveCanvas({ 297 | Key key, 298 | this.example, 299 | this.drawConfigValues, 300 | this.fillerConfigValues, 301 | this.fillerType, 302 | }) : super(key: key); 303 | 304 | @override 305 | Widget build(BuildContext context) { 306 | DrawConfig drawConfig = DrawConfig.build( 307 | maxRandomnessOffset: drawConfigValues['maxRandomnessOffset'], 308 | bowing: drawConfigValues['bowing'], 309 | roughness: drawConfigValues['roughness'], 310 | curveFitting: drawConfigValues['curveFitting'], 311 | curveTightness: drawConfigValues['curveTightness'], 312 | curveStepCount: drawConfigValues['curveStepCount'], 313 | seed: drawConfigValues['seed'].floor()); 314 | FillerConfig fillerConfig = FillerConfig.build( 315 | fillWeight: fillerConfigValues['fillWeight'], 316 | hachureAngle: fillerConfigValues['hachureAngle'], 317 | hachureGap: fillerConfigValues['hachureGap'], 318 | dashOffset: fillerConfigValues['dashOffset'], 319 | dashGap: fillerConfigValues['dashGap'], 320 | zigzagOffset: fillerConfigValues['zigzagOffset'], 321 | drawConfig: drawConfig, 322 | ); 323 | Filler filler = _fillers[fillerType].call(fillerConfig); 324 | return CustomPaint( 325 | size: const Size.square(double.infinity), 326 | painter: InteractivePainter(drawConfig, filler, example), 327 | ); 328 | } 329 | } 330 | 331 | class InteractivePainter extends CustomPainter { 332 | final DrawConfig drawConfig; 333 | final Filler filler; 334 | final InteractiveExample interactiveExample; 335 | 336 | InteractivePainter(this.drawConfig, this.filler, this.interactiveExample); 337 | 338 | @override 339 | paint(Canvas canvas, Size size) { 340 | drawConfig.randomizer.reset(); 341 | interactiveExample.paintRough(canvas, size, drawConfig, filler); 342 | } 343 | 344 | @override 345 | bool shouldRepaint(InteractivePainter oldDelegate) { 346 | return oldDelegate.drawConfig != drawConfig; 347 | } 348 | } 349 | 350 | abstract class InteractiveExample { 351 | void paintRough(Canvas canvas, Size size, DrawConfig drawConfig, Filler filler); 352 | } 353 | -------------------------------------------------------------------------------- /example/lib/interactive_examples.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:rough/rough.dart'; 5 | 6 | import 'interactive_canvas.dart'; 7 | 8 | class FlutterLogoExample extends InteractiveExample { 9 | Paint stroke = Paint() 10 | ..strokeWidth = 2 11 | ..isAntiAlias = true 12 | ..color = Colors.blueAccent 13 | ..strokeCap = StrokeCap.square 14 | ..style = PaintingStyle.stroke; 15 | 16 | Paint fillPaint = Paint() 17 | ..strokeWidth = 1 18 | ..isAntiAlias = true 19 | ..color = Colors.lightBlueAccent 20 | ..style = PaintingStyle.stroke; 21 | 22 | @override 23 | void paintRough(canvas, size, drawConfig, Filler filler) { 24 | Generator gen = Generator(drawConfig, filler); 25 | double logoWidth = 165; 26 | double logoHeight = 201; 27 | double widthScale = (size.width) / (logoWidth); 28 | double heightScale = (size.height) / (logoHeight); 29 | double scale = min(widthScale, heightScale); 30 | double translateX = (size.width - logoWidth * scale) / 2; 31 | double translateY = (size.height - logoHeight * scale) / 2; 32 | 33 | canvas 34 | ..translate(translateX, translateY) 35 | ..scale(scale) 36 | ..drawRough(gen.polygon([PointD(37, 128), PointD(9, 101), PointD(100, 10), PointD(156, 10)]), stroke, fillPaint) 37 | ..translate(-4, -4) 38 | ..drawRough(gen.polygon([PointD(156, 94), PointD(100, 94), PointD(50, 141), PointD(79, 170)]), stroke, fillPaint) 39 | ..translate(6, 6) 40 | ..drawRough(gen.polygon([PointD(79, 170), PointD(100, 191), PointD(156, 191), PointD(107, 142)]), stroke, fillPaint); 41 | } 42 | } 43 | 44 | class ArcExample extends InteractiveExample { 45 | final Paint pathPaint = Paint() 46 | ..color = Colors.red 47 | ..style = PaintingStyle.stroke 48 | ..isAntiAlias = true 49 | ..strokeWidth = 2; 50 | final Paint fillPaint = Paint() 51 | ..color = Colors.blue 52 | ..style = PaintingStyle.stroke 53 | ..isAntiAlias = true 54 | ..strokeWidth = 1; 55 | 56 | @override 57 | void paintRough(Canvas canvas, Size size, DrawConfig drawConfig, Filler filler) { 58 | Generator generator = Generator(drawConfig, filler); 59 | double s = min(size.width, size.height); 60 | Drawable figure = generator.arc(size.width / 2, size.height / 2, s * 0.8, s * 0.8, pi * 0.2, pi * 1.8, true); 61 | canvas.drawRough(figure, pathPaint, fillPaint); 62 | } 63 | } 64 | 65 | class CircleExample extends InteractiveExample { 66 | final Paint pathPaint = Paint() 67 | ..color = Colors.red 68 | ..style = PaintingStyle.stroke 69 | ..isAntiAlias = true 70 | ..strokeWidth = 2; 71 | final Paint fillPaint = Paint() 72 | ..color = Colors.blue 73 | ..style = PaintingStyle.stroke 74 | ..isAntiAlias = true 75 | ..strokeWidth = 1; 76 | 77 | @override 78 | void paintRough(Canvas canvas, Size size, DrawConfig drawConfig, Filler filler) { 79 | Generator generator = Generator(drawConfig, filler); 80 | double s = min(size.width, size.height); 81 | Drawable figure = generator.circle(size.width / 2, size.height / 2, s * 0.8); 82 | canvas.drawRough(figure, pathPaint, fillPaint); 83 | } 84 | } 85 | 86 | class RectangleExample extends InteractiveExample { 87 | final Paint pathPaint = Paint() 88 | ..color = Colors.lightGreen.withOpacity(0.8) 89 | ..style = PaintingStyle.stroke 90 | ..isAntiAlias = true 91 | ..strokeCap = StrokeCap.square 92 | ..strokeWidth = 5; 93 | final Paint fillPaint = Paint() 94 | ..color = Colors.blue 95 | ..style = PaintingStyle.stroke 96 | ..isAntiAlias = true 97 | ..strokeWidth = 1; 98 | 99 | @override 100 | void paintRough(Canvas canvas, Size size, DrawConfig drawConfig, Filler filler) { 101 | Generator generator = Generator(drawConfig, filler); 102 | 103 | Drawable figure = generator.rectangle(size.width * 0.1, size.height * 0.2, size.width * 0.8, size.height * 0.6); 104 | canvas.drawRough(figure, pathPaint, fillPaint); 105 | } 106 | } 107 | 108 | class CurveExample extends InteractiveExample { 109 | final Paint pathPaint = Paint() 110 | ..color = Colors.purpleAccent 111 | ..style = PaintingStyle.stroke 112 | ..isAntiAlias = true 113 | ..strokeCap = StrokeCap.square 114 | ..strokeWidth = 5; 115 | final Paint fillPaint = Paint() 116 | ..color = Colors.blue 117 | ..style = PaintingStyle.stroke 118 | ..isAntiAlias = true 119 | ..strokeWidth = 1; 120 | 121 | @override 122 | void paintRough(Canvas canvas, Size size, DrawConfig drawConfig, Filler filler) { 123 | List points = []; 124 | for (double i = 0; i <= pi * 6; i += pi / drawConfig.curveStepCount) { 125 | points 126 | ..add(PointD( 127 | size.width / 2 + sin(i) * 7 * i + drawConfig.offsetSymmetric(drawConfig.maxRandomnessOffset), 128 | size.height / 2 + cos(i) * 7 * i + drawConfig.offsetSymmetric(drawConfig.maxRandomnessOffset), 129 | )); 130 | } 131 | canvas.drawRough(Generator(drawConfig, filler).curvePath(points), pathPaint, fillPaint); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:RoughExample/pages/home_page.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | void main() => runApp(FlutterRoughDemo()); 5 | 6 | class FlutterRoughDemo extends StatelessWidget { 7 | // This widget is the root of your application. 8 | @override 9 | Widget build(BuildContext context) { 10 | return MaterialApp( 11 | title: 'Flutter Rough Demo', 12 | theme: ThemeData( 13 | primarySwatch: Colors.orange, 14 | ), 15 | home: HomePage(), 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /example/lib/pages/decoration_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:rough/rough.dart'; 4 | 5 | class DecorationExamplePage extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | return Scaffold( 9 | appBar: AppBar(title: const Text('RoughDecorator example')), 10 | body: Container( 11 | width: double.infinity, 12 | child: ListView( 13 | padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 32), 14 | children: [ 15 | const NiceBox(), 16 | const SizedBox(height: 32), 17 | const HighlightedText(), 18 | const SizedBox(height: 32), 19 | const CircledIcon(), 20 | const SizedBox(height: 32), 21 | const SecretText(), 22 | ], 23 | ), 24 | )); 25 | } 26 | } 27 | 28 | class NiceBox extends StatelessWidget { 29 | const NiceBox({ 30 | Key key, 31 | }) : super(key: key); 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | return Container( 36 | decoration: RoughBoxDecoration( 37 | shape: RoughBoxShape.rectangle, 38 | borderStyle: RoughDrawingStyle( 39 | width: 4, 40 | color: Colors.orange, 41 | ), 42 | filler: DotFiller(FillerConfig.build(hachureGap: 15, fillWeight: 10)), 43 | fillStyle: RoughDrawingStyle( 44 | width: 2, 45 | color: Colors.blue[100], 46 | )), 47 | child: const Text( 48 | 'BoxDecorator\ndecorating\na nice\nbox with text', 49 | style: TextStyle(fontSize: 20), 50 | textAlign: TextAlign.center, 51 | ), 52 | ); 53 | } 54 | } 55 | 56 | class HighlightedText extends StatelessWidget { 57 | const HighlightedText({ 58 | Key key, 59 | }) : super(key: key); 60 | 61 | @override 62 | Widget build(BuildContext context) { 63 | return Container( 64 | padding: const EdgeInsets.symmetric(vertical: 8), 65 | decoration: RoughBoxDecoration( 66 | shape: RoughBoxShape.rectangle, 67 | filler: ZigZagFiller(FillerConfig.defaultConfig.copyWith(hachureGap: 6, hachureAngle: 110)), 68 | fillStyle: RoughDrawingStyle(color: Colors.yellow[600], width: 6), 69 | ), 70 | child: const Text( 71 | 'Text remarked with a highlighter', 72 | textAlign: TextAlign.center, 73 | ), 74 | ); 75 | } 76 | } 77 | 78 | class CircledIcon extends StatelessWidget { 79 | const CircledIcon({ 80 | Key key, 81 | }) : super(key: key); 82 | 83 | @override 84 | Widget build(BuildContext context) { 85 | return Container( 86 | padding: const EdgeInsets.all(32), 87 | decoration: RoughBoxDecoration( 88 | shape: RoughBoxShape.circle, 89 | drawConfig: DrawConfig.build( 90 | roughness: 2, 91 | curveTightness: 0.1, 92 | curveFitting: 1, 93 | curveStepCount: 6, 94 | ), 95 | filler: SolidFiller(FillerConfig.defaultConfig), 96 | borderStyle: RoughDrawingStyle( 97 | color: Colors.lightGreen, 98 | width: 6, 99 | ), 100 | ), 101 | child: Icon(Icons.format_paint), 102 | ); 103 | } 104 | } 105 | 106 | class SecretText extends StatelessWidget { 107 | const SecretText({ 108 | Key key, 109 | }) : super(key: key); 110 | 111 | @override 112 | Widget build(BuildContext context) { 113 | return RichText( 114 | text: TextSpan( 115 | style: TextStyle(color: Colors.black, fontSize: 18), 116 | text: 'This text has a ', 117 | children: [ 118 | WidgetSpan( 119 | child: Container( 120 | decoration: RoughBoxDecoration( 121 | shape: RoughBoxShape.rectangle, 122 | drawConfig: DrawConfig.build(), 123 | filler: HatchFiller(FillerConfig.build( 124 | hachureAngle: 20, 125 | hachureGap: 5, 126 | drawConfig: DrawConfig.build(roughness: 3), 127 | )), 128 | fillStyle: RoughDrawingStyle( 129 | color: Colors.brown, 130 | width: 2, 131 | )), 132 | child: Text( 133 | 'secret text', 134 | style: TextStyle(color: Colors.black, fontSize: 18), 135 | ), 136 | )), 137 | const TextSpan(text: ' that you can not read'), 138 | ], 139 | ), 140 | ); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /example/lib/pages/home_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import '../demos.dart'; 5 | 6 | class HomePage extends StatelessWidget { 7 | @override 8 | Widget build(BuildContext context) { 9 | return Scaffold( 10 | appBar: AppBar(title: const Text('Flutter Rough example')), 11 | body: DemoList(), 12 | ); 13 | } 14 | } 15 | 16 | class DemoList extends StatelessWidget { 17 | @override 18 | Widget build(BuildContext context) { 19 | return Container( 20 | child: ListView.separated( 21 | separatorBuilder: (context, position) => Container( 22 | color: Theme.of(context).cardColor, 23 | child: const Divider( 24 | indent: 64, 25 | thickness: 1, 26 | height: 4, 27 | ), 28 | ), 29 | itemCount: demos.length, 30 | itemBuilder: (context, position) => DemoRow(demo: demos[position]), 31 | ), 32 | ); 33 | } 34 | } 35 | 36 | class DemoRow extends StatelessWidget { 37 | final Demo demo; 38 | 39 | const DemoRow({Key key, this.demo}) : super(key: key); 40 | 41 | @override 42 | Widget build(BuildContext context) { 43 | return Container( 44 | color: Theme.of(context).cardColor, 45 | child: ListTile( 46 | contentPadding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), 47 | title: Text(demo.name), 48 | dense: false, 49 | subtitle: Text(demo.description), 50 | leading: Container( 51 | child: demo.icon, 52 | width: 42, 53 | height: 42, 54 | ), 55 | onTap: () => Navigator.push( 56 | context, 57 | MaterialPageRoute( 58 | builder: demo.buildPage, 59 | ), 60 | ), 61 | ), 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /example/lib/pages/interactive_example_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import '../interactive_canvas.dart'; 5 | 6 | class ExamplePage extends StatelessWidget { 7 | final String title; 8 | final InteractiveExample Function() exampleBuilder; 9 | 10 | const ExamplePage({Key key, this.title, this.exampleBuilder}) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Scaffold( 15 | appBar: AppBar(title: Text(title)), 16 | body: InteractiveBody( 17 | example: exampleBuilder(), 18 | ), 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | archive: 5 | dependency: transitive 6 | description: 7 | name: archive 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.0.13" 11 | args: 12 | dependency: transitive 13 | description: 14 | name: args 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "1.6.0" 18 | async: 19 | dependency: transitive 20 | description: 21 | name: async 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.4.1" 25 | boolean_selector: 26 | dependency: transitive 27 | description: 28 | name: boolean_selector 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "2.0.0" 32 | charcode: 33 | dependency: transitive 34 | description: 35 | name: charcode 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.3" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.14.12" 46 | convert: 47 | dependency: transitive 48 | description: 49 | name: convert 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "2.1.1" 53 | crypto: 54 | dependency: transitive 55 | description: 56 | name: crypto 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "2.1.4" 60 | flutter: 61 | dependency: "direct main" 62 | description: flutter 63 | source: sdk 64 | version: "0.0.0" 65 | flutter_test: 66 | dependency: "direct dev" 67 | description: flutter 68 | source: sdk 69 | version: "0.0.0" 70 | image: 71 | dependency: transitive 72 | description: 73 | name: image 74 | url: "https://pub.dartlang.org" 75 | source: hosted 76 | version: "2.1.12" 77 | matcher: 78 | dependency: transitive 79 | description: 80 | name: matcher 81 | url: "https://pub.dartlang.org" 82 | source: hosted 83 | version: "0.12.6" 84 | meta: 85 | dependency: transitive 86 | description: 87 | name: meta 88 | url: "https://pub.dartlang.org" 89 | source: hosted 90 | version: "1.1.8" 91 | path: 92 | dependency: transitive 93 | description: 94 | name: path 95 | url: "https://pub.dartlang.org" 96 | source: hosted 97 | version: "1.6.4" 98 | petitparser: 99 | dependency: transitive 100 | description: 101 | name: petitparser 102 | url: "https://pub.dartlang.org" 103 | source: hosted 104 | version: "2.4.0" 105 | quiver: 106 | dependency: transitive 107 | description: 108 | name: quiver 109 | url: "https://pub.dartlang.org" 110 | source: hosted 111 | version: "2.1.3" 112 | rough: 113 | dependency: "direct main" 114 | description: 115 | path: ".." 116 | relative: true 117 | source: path 118 | version: "0.1.1" 119 | sky_engine: 120 | dependency: transitive 121 | description: flutter 122 | source: sdk 123 | version: "0.0.99" 124 | source_span: 125 | dependency: transitive 126 | description: 127 | name: source_span 128 | url: "https://pub.dartlang.org" 129 | source: hosted 130 | version: "1.7.0" 131 | stack_trace: 132 | dependency: transitive 133 | description: 134 | name: stack_trace 135 | url: "https://pub.dartlang.org" 136 | source: hosted 137 | version: "1.9.3" 138 | stream_channel: 139 | dependency: transitive 140 | description: 141 | name: stream_channel 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "2.0.0" 145 | string_scanner: 146 | dependency: transitive 147 | description: 148 | name: string_scanner 149 | url: "https://pub.dartlang.org" 150 | source: hosted 151 | version: "1.0.5" 152 | term_glyph: 153 | dependency: transitive 154 | description: 155 | name: term_glyph 156 | url: "https://pub.dartlang.org" 157 | source: hosted 158 | version: "1.1.0" 159 | test_api: 160 | dependency: transitive 161 | description: 162 | name: test_api 163 | url: "https://pub.dartlang.org" 164 | source: hosted 165 | version: "0.2.15" 166 | typed_data: 167 | dependency: transitive 168 | description: 169 | name: typed_data 170 | url: "https://pub.dartlang.org" 171 | source: hosted 172 | version: "1.1.6" 173 | vector_math: 174 | dependency: transitive 175 | description: 176 | name: vector_math 177 | url: "https://pub.dartlang.org" 178 | source: hosted 179 | version: "2.0.8" 180 | xml: 181 | dependency: transitive 182 | description: 183 | name: xml 184 | url: "https://pub.dartlang.org" 185 | source: hosted 186 | version: "3.6.1" 187 | sdks: 188 | dart: ">=2.6.0 <3.0.0" 189 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: RoughExample 2 | description: A example of the rough library 3 | 4 | version: 0.1.1 5 | 6 | environment: 7 | sdk: ">=2.6.0 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | rough: 13 | path: ../ 14 | dev_dependencies: 15 | flutter_test: 16 | sdk: flutter 17 | 18 | flutter: 19 | uses-material-design: true 20 | -------------------------------------------------------------------------------- /lib/rough.dart: -------------------------------------------------------------------------------- 1 | library rough; 2 | 3 | export 'src/config.dart'; 4 | export 'src/decoration.dart'; 5 | export 'src/entities.dart'; 6 | export 'src/filler.dart'; 7 | export 'src/generator.dart'; 8 | export 'src/rough.dart'; 9 | -------------------------------------------------------------------------------- /lib/src/config.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | // Describe how a particular shape is drawn. 4 | class DrawConfig { 5 | final double maxRandomnessOffset; 6 | final double roughness; 7 | final double bowing; 8 | final double curveFitting; 9 | final double curveTightness; 10 | final double curveStepCount; 11 | final int seed; 12 | final Randomizer randomizer; 13 | 14 | static DrawConfig defaultValues = DrawConfig.build( 15 | maxRandomnessOffset: 2, 16 | roughness: 1, 17 | bowing: 1, 18 | curveFitting: 0.95, 19 | curveTightness: 0, 20 | curveStepCount: 9, 21 | seed: 1, 22 | ); 23 | 24 | const DrawConfig._({ 25 | this.maxRandomnessOffset, 26 | this.roughness, 27 | this.bowing, 28 | this.curveFitting, 29 | this.curveTightness, 30 | this.curveStepCount, 31 | this.seed, 32 | this.randomizer, 33 | }); 34 | 35 | /// Generates a [DrawConfig] 36 | /// * [roughness] Numerical value indicating how rough the drawing is. A rectangle with the roughness of 0 would be a perfect rectangle. Default value is 1. There is no upper limit to this value, but a value over 10 is mostly useless. 37 | /// * [bowing] Numerical value indicating how curvy the lines are when drawing a sketch. A value of 0 will cause straight lines. Default value is 1. 38 | /// * [seed] The seed for creating random values used in shape generation. This is useful for creating the exact shape when re-generating with the same parameters. Default value is 1. 39 | /// * [curveStepCount] When drawing ellipses, circles, and arcs, Rough approximates [curveStepCount] number of points to estimate the shape. Default value is 9. 40 | /// * [curveTightness] 41 | /// * [curveFitting] When drawing ellipses, circles, and arcs, it means how close should the rendered dimensions be when compared to the specified one. Default value is 0.95. 42 | static DrawConfig build({ 43 | double maxRandomnessOffset, 44 | double roughness, 45 | double bowing, 46 | double curveFitting, 47 | double curveTightness, 48 | double curveStepCount, 49 | int seed, 50 | }) => 51 | DrawConfig._( 52 | maxRandomnessOffset: maxRandomnessOffset ?? defaultValues.maxRandomnessOffset, 53 | roughness: roughness ?? defaultValues.roughness, 54 | bowing: bowing ?? defaultValues.bowing, 55 | curveFitting: curveFitting ?? defaultValues.curveFitting, 56 | curveTightness: curveTightness ?? defaultValues.curveTightness, 57 | curveStepCount: curveStepCount ?? defaultValues.curveStepCount, 58 | seed: seed ?? defaultValues.seed, 59 | randomizer: Randomizer(seed: seed ?? defaultValues.seed)); 60 | 61 | double offset(double min, double max, [double roughnessGain = 1]) { 62 | return roughness * roughnessGain * ((randomizer.next() * (max - min)) + min); 63 | } 64 | 65 | double offsetSymmetric(double x, [double roughnessGain = 1]) { 66 | return offset(-x, x, roughnessGain); 67 | } 68 | 69 | DrawConfig copyWith({ 70 | double maxRandomnessOffset, 71 | double roughness, 72 | double bowing, 73 | double curveFitting, 74 | double curveTightness, 75 | double curveStepCount, 76 | double fillWeight, 77 | int seed, 78 | bool combineNestedSvgPaths, 79 | Randomizer randomizer, 80 | }) => 81 | DrawConfig._( 82 | maxRandomnessOffset: maxRandomnessOffset ?? this.maxRandomnessOffset, 83 | roughness: roughness ?? this.roughness, 84 | bowing: bowing ?? this.bowing, 85 | curveFitting: curveFitting ?? this.curveFitting, 86 | curveTightness: curveTightness ?? this.curveTightness, 87 | curveStepCount: curveStepCount ?? this.curveStepCount, 88 | seed: seed ?? this.seed, 89 | randomizer: randomizer ?? (this.randomizer == null ? null : Randomizer(seed: this.randomizer.seed))); 90 | 91 | @override 92 | bool operator ==(Object other) => 93 | identical(this, other) || 94 | other is DrawConfig && 95 | runtimeType == other.runtimeType && 96 | maxRandomnessOffset == other.maxRandomnessOffset && 97 | roughness == other.roughness && 98 | bowing == other.bowing && 99 | curveFitting == other.curveFitting && 100 | curveTightness == other.curveTightness && 101 | curveStepCount == other.curveStepCount && 102 | seed == other.seed && 103 | randomizer == other.randomizer; 104 | 105 | @override 106 | int get hashCode => 107 | maxRandomnessOffset.hashCode ^ 108 | roughness.hashCode ^ 109 | bowing.hashCode ^ 110 | curveFitting.hashCode ^ 111 | curveTightness.hashCode ^ 112 | curveStepCount.hashCode ^ 113 | seed.hashCode ^ 114 | randomizer.hashCode; 115 | } 116 | 117 | class Randomizer { 118 | Random _random; 119 | int _seed; 120 | 121 | Randomizer({int seed = 0}) { 122 | _seed = seed; 123 | _random = Random(seed); 124 | } 125 | 126 | int get seed => _seed; 127 | 128 | double next() => _random.nextDouble(); 129 | 130 | void reset() { 131 | _random = Random(_seed); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /lib/src/core.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'entities.dart'; 4 | import 'geometry.dart'; 5 | 6 | class Op { 7 | final OpType op; 8 | final List data; 9 | 10 | Op.move(PointD point) 11 | : op = OpType.move, 12 | data = [point]; 13 | 14 | Op.lineTo(PointD point) 15 | : op = OpType.lineTo, 16 | data = [point]; 17 | 18 | Op.curveTo(PointD control1, PointD control2, PointD destination) 19 | : op = OpType.curveTo, 20 | data = [control1, control2, destination]; 21 | } 22 | 23 | class OpSet { 24 | OpSetType type; 25 | List ops; 26 | 27 | OpSet({this.type, this.ops}); 28 | } 29 | 30 | enum OpType { move, curveTo, lineTo } 31 | enum OpSetType { path, fillPath, fillSketch } 32 | 33 | class Line { 34 | PointD source; 35 | PointD target; 36 | 37 | Line(this.source, this.target); 38 | 39 | double get length => sqrt(pow(source.x - target.x, 2) + pow(source.y - target.y, 2)); 40 | 41 | bool onSegment(PointD point) => 42 | point.x <= max(source.x, target.x) && 43 | point.x >= min(source.x, target.x) && 44 | point.y <= max(source.y, target.y) && 45 | point.y >= min(source.y, target.y); 46 | 47 | bool intersects(Line line) { 48 | final PointsOrientation o1 = getOrientation(source, target, line.source); 49 | final PointsOrientation o2 = getOrientation(source, target, line.target); 50 | final PointsOrientation o3 = getOrientation(line.source, line.target, source); 51 | final PointsOrientation o4 = getOrientation(line.source, line.target, target); 52 | 53 | if (o1 != o2 && o3 != o4) { 54 | return true; 55 | } 56 | // source, target and line.source are colinear and line.source lies on segment this.source-this.target 57 | 58 | if (o1 == PointsOrientation.collinear && onSegmentPoints(source, line.source, target)) { 59 | return true; 60 | } 61 | 62 | // source, target and line.source are collinear and line.target lies on segment source-target 63 | if (o2 == PointsOrientation.collinear && onSegmentPoints(source, line.target, target)) { 64 | return true; 65 | } 66 | 67 | // line.source, line.target and source are collinear and source lies on segment line.source-line.target 68 | if (o3 == PointsOrientation.collinear && onSegmentPoints(line.source, source, line.target)) { 69 | return true; 70 | } 71 | 72 | // line.source, line.target and target are collinear and target lies on segment line.source-line.target 73 | if (o4 == PointsOrientation.collinear && onSegmentPoints(line.source, target, line.target)) { 74 | return true; 75 | } 76 | return false; 77 | } 78 | 79 | PointD intersectionWith(Line line) { 80 | final double yDiff = target.y - source.y; 81 | final double xDiff = source.x - target.x; 82 | final double diff = yDiff * (source.x) + xDiff * (source.y); 83 | final double lineYDiff = line.target.y - line.source.y; 84 | final double lineXDiff = line.source.x - line.target.x; 85 | final double lineDiff = lineYDiff * (line.source.x) + lineXDiff * (line.source.y); 86 | final double determinant = yDiff * lineXDiff - lineYDiff * xDiff; 87 | return determinant == 0 88 | ? PointD((lineXDiff * diff - xDiff * lineDiff) / determinant, (yDiff * lineDiff - lineYDiff * diff) / determinant) 89 | : null; 90 | } 91 | 92 | bool isMidPointInPolygon(List polygon) { 93 | return PointD((source.x + target.x) / 2, (source.y + target.y) / 2).isInPolygon(polygon); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /lib/src/decoration.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | import 'dart:ui'; 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:rough/rough.dart'; 6 | 7 | import 'config.dart'; 8 | 9 | class RoughDrawingStyle { 10 | final double width; 11 | final Color color; 12 | final Gradient gradient; 13 | final BlendMode blendMode; 14 | // TODO: final BorderRadius borderRadius; 15 | // TODO: this.boxShadow?, 16 | 17 | const RoughDrawingStyle({ 18 | this.width, 19 | this.color, 20 | this.gradient, 21 | this.blendMode, 22 | }); 23 | } 24 | 25 | /// The shape to use when rendering a [RoughBoxDecoration]. 26 | /// 27 | enum RoughBoxShape { 28 | /// An axis-aligned, 2D rectangle. May have rounded corners (described by a 29 | /// [BorderRadius]). The center of edges of the rectangle will match the 30 | /// edges of the box into which the [RoughBoxDecoration] is painted. 31 | rectangle, 32 | 33 | /// A circle centered in the middle of the box into which the [Border] or 34 | /// [BoxDecoration] is painted. The diameter of the circle is the shortest 35 | /// dimension of the box, either the width or the height, such that the circle 36 | /// path center touches the edges of the box. 37 | circle, 38 | 39 | /// An ellipse centered in the middle of the box into which the [Border] or 40 | /// [BoxDecoration] is painted. The horizontal diameter of the ellipse is the width 41 | /// the box and the vertical diameter is the height of the box, such that the ellipse 42 | /// path center touches the edges of the box. 43 | ellipse, 44 | } 45 | 46 | class RoughBoxDecoration extends Decoration { 47 | final RoughBoxShape shape; 48 | final RoughDrawingStyle borderStyle; 49 | final DrawConfig drawConfig; 50 | final RoughDrawingStyle fillStyle; 51 | final Filler filler; 52 | const RoughBoxDecoration({ 53 | this.borderStyle, 54 | this.drawConfig, 55 | this.fillStyle, 56 | this.shape = RoughBoxShape.rectangle, 57 | this.filler, 58 | }) : assert(shape != null); 59 | 60 | @override 61 | EdgeInsetsGeometry get padding => EdgeInsets.all(max(0.1, (borderStyle?.width ?? 0.1) / 2)); 62 | 63 | @override 64 | BoxPainter createBoxPainter([VoidCallback onChanged]) { 65 | return RoughDecorationPainter(this); 66 | } 67 | } 68 | 69 | class RoughDecorationPainter extends BoxPainter { 70 | final RoughBoxDecoration roughDecoration; 71 | 72 | RoughDecorationPainter( 73 | this.roughDecoration, 74 | ) : assert(roughDecoration != null); 75 | 76 | @override 77 | void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) { 78 | final DrawConfig drawConfig = roughDecoration.drawConfig ?? DrawConfig.defaultValues; 79 | final Filler filler = roughDecoration.filler ?? NoFiller(); 80 | final Generator generator = Generator(drawConfig, filler); 81 | final Rect rect = offset & configuration.size; 82 | 83 | final Paint borderPaint = _buildDrawPaint(roughDecoration.borderStyle, rect); 84 | 85 | final Paint fillPaint = roughDecoration.fillStyle == null ? borderPaint : _buildDrawPaint(roughDecoration.fillStyle, rect); 86 | 87 | Drawable drawable; 88 | switch (roughDecoration.shape) { 89 | case RoughBoxShape.rectangle: 90 | drawable = generator.rectangle(offset.dx, offset.dy, configuration.size.width, configuration.size.height); 91 | break; 92 | case RoughBoxShape.circle: 93 | final double centerX = offset.dx + configuration.size.width / 2; 94 | final double centerY = offset.dy + configuration.size.height / 2; 95 | final double diameter = configuration.size.shortestSide; 96 | drawable = generator.circle(centerX, centerY, diameter); 97 | break; 98 | case RoughBoxShape.ellipse: 99 | final double centerX = offset.dx + configuration.size.width / 2; 100 | final double centerY = offset.dy + configuration.size.height / 2; 101 | 102 | drawable = generator.ellipse(centerX, centerY, configuration.size.width, configuration.size.height); 103 | break; 104 | } 105 | canvas.drawRough(drawable, borderPaint, fillPaint); 106 | } 107 | 108 | Paint _buildDrawPaint(RoughDrawingStyle roughDrawDecoration, Rect rect) { 109 | const defaultColor = Color(0x00000000); 110 | final Paint paint = Paint() 111 | ..style = PaintingStyle.stroke 112 | ..isAntiAlias = true 113 | ..strokeCap = StrokeCap.square 114 | ..strokeWidth = roughDrawDecoration?.width ?? 0.1 115 | ..color = roughDrawDecoration?.color ?? defaultColor 116 | ..shader = roughDrawDecoration?.gradient?.createShader(rect); 117 | if (roughDrawDecoration?.blendMode != null) { 118 | paint.blendMode = roughDrawDecoration.blendMode; 119 | } 120 | return paint; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /lib/src/entities.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'config.dart'; 4 | import 'core.dart'; 5 | import 'geometry.dart'; 6 | 7 | class Drawable { 8 | String shape; 9 | DrawConfig options; 10 | List sets; 11 | 12 | Drawable({this.shape, this.options, this.sets}); 13 | } 14 | 15 | class PointD extends Point { 16 | PointD(double x, double y) : super(x, y); 17 | 18 | bool isInPolygon(List points) { 19 | final int vertices = points.length; 20 | 21 | // There must be at least 3 vertices in polygon 22 | if (vertices < 3) { 23 | return false; 24 | } 25 | final PointD extreme = PointD(double.maxFinite, y); 26 | int count = 0; 27 | for (int i = 0; i < vertices; i++) { 28 | final PointD current = points[i]; 29 | final PointD next = points[(i + 1) % vertices]; 30 | if (Line(current, next).intersects(Line(this, extreme))) { 31 | if (getOrientation(current, this, next) == PointsOrientation.collinear) { 32 | return Line(current, next).onSegment(this); 33 | } 34 | count++; 35 | } 36 | } 37 | // true if count is off 38 | return count % 2 == 1; 39 | } 40 | 41 | @override 42 | String toString() { 43 | return 'PointD{x:$x, y:$y}'; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/src/filler.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:rough/rough.dart'; 4 | 5 | import 'config.dart'; 6 | import 'core.dart'; 7 | import 'entities.dart'; 8 | import 'geometry.dart'; 9 | import 'renderer.dart'; 10 | 11 | class IntersectionInfo { 12 | PointD point; 13 | double distance; 14 | 15 | IntersectionInfo({this.point, this.distance}); 16 | } 17 | 18 | enum FillStyle { fill, sketch } 19 | 20 | class FillerConfig { 21 | final DrawConfig _drawConfig; 22 | final double fillWeight; 23 | final double hachureAngle; 24 | final double hachureGap; 25 | final double dashOffset; 26 | final double dashGap; 27 | final double zigzagOffset; 28 | 29 | const FillerConfig._({ 30 | DrawConfig drawConfig, 31 | this.fillWeight, 32 | this.hachureAngle, 33 | this.hachureGap, 34 | this.dashOffset, 35 | this.dashGap, 36 | this.zigzagOffset, 37 | }) : _drawConfig = drawConfig; 38 | 39 | /// * [fillWeight] When using dots styles to fill the shape, this value represents the diameter of the dot. 40 | /// * [hachureAngle] Numerical value (in degrees) that defines the angle of the hachure lines. Default value is -41 degrees. 41 | /// * [hachureGap] Numerical value that defines the average gap, in pixels, between two hachure lines. Default value is 15. 42 | /// * [dashOffset] When filling a shape using the [DashedFiller], this property indicates the nominal length of dash (in pixels). If not set, it defaults to the hachureGap value. 43 | /// * [dashGap] When filling a shape using the [DashedFiller], this property indicates the nominal gap between dashes (in pixels). If not set, it defaults to the hachureGap value. 44 | /// * [zigzagOffset] When filling a shape using the [ZigZagLineFiller], this property indicates the nominal width of the zig-zag triangle in each line. If not set, it defaults to the hachureGap value. 45 | static FillerConfig build({ 46 | DrawConfig drawConfig, 47 | double fillWeight = 1, 48 | double hachureAngle = 320, 49 | double hachureGap = 15, 50 | double dashOffset = 15, 51 | double dashGap = 2, 52 | double zigzagOffset = 5, 53 | }) => 54 | FillerConfig._( 55 | drawConfig: drawConfig ?? DrawConfig.build(), 56 | fillWeight: fillWeight, 57 | hachureAngle: hachureAngle, 58 | hachureGap: hachureGap, 59 | dashOffset: dashOffset, 60 | dashGap: dashGap, 61 | zigzagOffset: zigzagOffset, 62 | ); 63 | 64 | static FillerConfig defaultConfig = FillerConfig.build(drawConfig: DrawConfig.defaultValues); 65 | 66 | DrawConfig get drawConfig => _drawConfig; 67 | 68 | FillerConfig copyWith({ 69 | DrawConfig drawConfig, 70 | double fillWeight, 71 | double hachureAngle, 72 | double hachureGap, 73 | double dashOffset, 74 | double dashGap, 75 | double zigzagOffset, 76 | }) => 77 | FillerConfig._( 78 | drawConfig: drawConfig ?? _drawConfig, 79 | fillWeight: fillWeight ?? this.fillWeight, 80 | hachureAngle: hachureAngle ?? this.hachureAngle, 81 | hachureGap: hachureGap ?? this.hachureGap, 82 | dashOffset: dashOffset ?? this.dashOffset, 83 | dashGap: dashGap ?? this.dashGap, 84 | zigzagOffset: zigzagOffset ?? this.zigzagOffset, 85 | ); 86 | } 87 | 88 | abstract class Filler { 89 | FillerConfig _config; 90 | 91 | Filler(FillerConfig config) { 92 | _config = config ?? FillerConfig.defaultConfig; 93 | } 94 | 95 | OpSet fill(List points); 96 | 97 | List buildFillLines(List points, FillerConfig config) { 98 | final _config = config ?? FillerConfig.defaultConfig; 99 | final PointD rotationCenter = PointD(0, 0); 100 | final double angle = (_config.hachureAngle + 90).roundToDouble(); 101 | if (angle != 0) { 102 | // ignore: parameter_assignments 103 | points = rotatePoints(points, rotationCenter, angle); 104 | } 105 | List lines = _straightenLines(points); 106 | if (angle != 0) { 107 | lines = rotateLines(lines, rotationCenter, -angle); 108 | } 109 | return lines; 110 | } 111 | 112 | List _straightenLines(List points) { 113 | final List vertices = points ?? []; 114 | final List lines = []; 115 | if (vertices[0] != vertices[vertices.length - 1]) { 116 | vertices.add(vertices[0]); 117 | } 118 | if (vertices.length > 2) { 119 | double gap = _config.hachureGap; 120 | gap = max(gap, 0.1); 121 | 122 | final List edges = createdSortedEdges(vertices); 123 | if (edges.isEmpty) { 124 | return lines; 125 | } 126 | // Start scanning 127 | List activeEdges = []; 128 | double y = edges[0].yMin; 129 | while (activeEdges.isNotEmpty || edges.isNotEmpty) { 130 | if (edges.isNotEmpty) { 131 | int ix = -1; 132 | for (int i = 0; i < edges.length; i++) { 133 | if (edges[i].yMin > y) { 134 | break; 135 | } 136 | ix = i; 137 | } 138 | final List removed = edges.sublist(0, ix + 1); 139 | edges.removeRange(0, ix + 1); 140 | // ignore: prefer_final_in_for_each 141 | for (Edge edge in removed) { 142 | activeEdges.add(ActiveEdge(y, edge)); 143 | } 144 | } 145 | activeEdges = activeEdges.where((ae) => ae.edge.yMax > y).toList(); 146 | 147 | // ignore: cascade_invocations 148 | activeEdges.sort((ae1, ae2) => ae1.edge.x.compareTo(ae2.edge.x)); 149 | 150 | // fill between the edges 151 | if (activeEdges.length > 1) { 152 | for (int i = 0; i < activeEdges.length; i = i + 2) { 153 | final int next = i + 1; 154 | if (next >= activeEdges.length) { 155 | break; 156 | } 157 | final Edge ce = activeEdges[i].edge; 158 | final Edge ne = activeEdges[next].edge; 159 | lines.add(Line(PointD(ce.x.roundToDouble(), y), PointD(ne.x.roundToDouble(), y))); 160 | } 161 | } 162 | 163 | y += gap; 164 | activeEdges = activeEdges.map((ae) { 165 | return ActiveEdge(ae.s, ae.edge.copyWith(x: ae.edge.x + (gap * ae.edge.slope))); 166 | }).toList(); 167 | } 168 | } 169 | return lines; 170 | } 171 | 172 | List createdSortedEdges(List vertices) { 173 | // Create sorted edges table 174 | final List edges = []; 175 | for (int i = 0; i < vertices.length - 1; i++) { 176 | final PointD p1 = vertices[i]; 177 | final PointD p2 = vertices[i + 1]; 178 | if (p1.y != p2.y) { 179 | final double yMin = min(p1.y, p2.y); 180 | edges.add(Edge( 181 | yMin: yMin, 182 | yMax: max(p1.y, p2.y), 183 | x: yMin == p1.y ? p1.x : p2.x, 184 | slope: (p2.x - p1.x) / (p2.y - p1.y), 185 | )); 186 | } 187 | } 188 | edges.sort(edgeSorter); 189 | return edges; 190 | } 191 | 192 | int edgeSorter(Edge e1, Edge e2) { 193 | if (e1.yMin < e2.yMin) { 194 | return -1; 195 | } 196 | if (e1.yMin > e2.yMin) { 197 | return 1; 198 | } 199 | if (e1.x < e2.x) { 200 | return -1; 201 | } 202 | if (e1.x > e2.x) { 203 | return 1; 204 | } 205 | return e1.yMax.compareTo(e2.yMax); 206 | } 207 | 208 | OpSet fillPolygon(List points, FillerConfig config, bool connectEnds) { 209 | List lines = buildFillLines(points, config); 210 | if (connectEnds) { 211 | final List connectingLines = connectLines(points, lines); 212 | lines += connectingLines; 213 | } 214 | final List ops = renderLines(lines, config); 215 | return OpSet(type: OpSetType.fillSketch, ops: ops); 216 | } 217 | 218 | List connectLines(List polygon, List lines) { 219 | final List result = []; 220 | if (lines.length > 1) { 221 | for (int i = 1; i < lines.length; i++) { 222 | final Line prev = lines[i - 1]; 223 | if (prev.length < 3) { 224 | continue; 225 | } 226 | final Line current = lines[i]; 227 | final Line segment = Line(current.source, prev.target); 228 | if (segment.length > 3) { 229 | final List segSplits = splitOnIntersections(polygon, segment); 230 | result.addAll(segSplits); 231 | } 232 | } 233 | } 234 | return result; 235 | } 236 | 237 | List splitOnIntersections(List polygon, Line segment) { 238 | final double error = max(5, segment.length * 0.1); 239 | final List intersections = []; 240 | for (int i = 0; i < polygon.length; i++) { 241 | final PointD p1 = polygon[i]; 242 | final PointD p2 = polygon[(i + 1) % polygon.length]; 243 | final Line polygonSegment = Line(p1, p2); 244 | if (segment.intersects(polygonSegment)) { 245 | final PointD ip = segment.intersectionWith(polygonSegment); 246 | if (ip != null) { 247 | final double d0 = Line(ip, segment.source).length; 248 | final double d1 = Line(ip, segment.target).length; 249 | if (d0 > error && d1 > error) { 250 | intersections.add(IntersectionInfo(point: ip, distance: d0)); 251 | } 252 | } 253 | } 254 | } 255 | if (intersections.length > 1) { 256 | intersections.sort((a, b) => (a.distance - b.distance).ceil()); 257 | final List intersectionPoints = intersections.map((d) => d.point).toList(); 258 | if (segment.source.isInPolygon(polygon)) { 259 | intersectionPoints.removeAt(0); 260 | } 261 | if (segment.target.isInPolygon(polygon)) { 262 | intersectionPoints.removeLast(); 263 | } 264 | if (intersectionPoints.length <= 1) { 265 | if (segment.isMidPointInPolygon(polygon)) { 266 | return [segment]; 267 | } else { 268 | return []; 269 | } 270 | } 271 | final List splitPoints = [segment.source] + intersectionPoints + [segment.target]; 272 | final List splitLines = []; 273 | for (int i = 0; i < (splitPoints.length - 1); i += 2) { 274 | final Line subSegment = Line(splitPoints[i], splitPoints[i + 1]); 275 | if (subSegment.isMidPointInPolygon(polygon)) { 276 | splitLines.add(subSegment); 277 | } 278 | } 279 | return splitLines; 280 | } else if (segment.isMidPointInPolygon(polygon)) { 281 | return [segment]; 282 | } else { 283 | return []; 284 | } 285 | } 286 | 287 | List renderLines(List lines, FillerConfig config) { 288 | final List ops = []; 289 | for (final Line line in lines) { 290 | ops.addAll( 291 | OpSetBuilder.buildLine( 292 | line.source.x, 293 | line.source.y, 294 | line.target.x, 295 | line.target.y, 296 | config.drawConfig, 297 | ).ops, 298 | ); 299 | } 300 | return ops; 301 | } 302 | } 303 | 304 | class NoFiller extends Filler { 305 | NoFiller([FillerConfig config]) : super(config); 306 | 307 | @override 308 | OpSet fill(List points) { 309 | return OpSet(type: OpSetType.fillSketch, ops: []); 310 | } 311 | } 312 | 313 | class HachureFiller extends Filler { 314 | HachureFiller([FillerConfig config]) : super(config); 315 | 316 | @override 317 | OpSet fill(List points) { 318 | return fillPolygon(points, _config, false); 319 | } 320 | } 321 | 322 | class ZigZagFiller extends Filler { 323 | ZigZagFiller([FillerConfig config]) : super(config); 324 | 325 | @override 326 | OpSet fill(List points) { 327 | return fillPolygon(points, _config, true); 328 | } 329 | } 330 | 331 | class HatchFiller extends Filler { 332 | HatchFiller([FillerConfig config]) : super(config); 333 | 334 | @override 335 | OpSet fill(List points) { 336 | final OpSet set1 = fillPolygon(points, _config, false); 337 | final FillerConfig rotated = _config.copyWith(hachureAngle: _config.hachureAngle + 90); 338 | final OpSet set2 = fillPolygon(points, rotated, false); 339 | return OpSet(type: OpSetType.fillSketch, ops: set1.ops + set2.ops); 340 | } 341 | } 342 | 343 | class DashedFiller extends Filler { 344 | DashedFiller([FillerConfig config]) : super(config); 345 | 346 | @override 347 | OpSet fill(List points) { 348 | final List lines = buildFillLines(points, _config); 349 | return OpSet(type: OpSetType.fillSketch, ops: dashedLines(lines, _config)); 350 | } 351 | 352 | List dashedLines(List lines, FillerConfig config) { 353 | final double offset = config.dashOffset; 354 | final double gap = config.dashGap; 355 | final List ops = []; 356 | for (final Line line in lines) { 357 | final double length = line.length; 358 | final int count = (length / (offset + gap)).floor(); 359 | final double lineOffset = (length + gap - (count * (offset + gap))) / 2; 360 | PointD lineStart = line.source; 361 | PointD lineEnd = line.target; 362 | if (lineStart.x > lineEnd.x) { 363 | lineStart = line.target; 364 | lineEnd = line.source; 365 | } 366 | final double alpha = atan((lineEnd.y - lineStart.y) / (lineEnd.x - lineStart.x)); 367 | for (int i = 0; i < count; i++) { 368 | final double segmentStartOffset = i * (offset + gap); 369 | 370 | final double segmentStartX = lineStart.x + (segmentStartOffset * cos(alpha)) + (lineOffset * cos(alpha)); 371 | final double segmentStartY = lineStart.y + segmentStartOffset * sin(alpha) + (lineOffset * sin(alpha)); 372 | final PointD gapStart = PointD(segmentStartX, segmentStartY); 373 | 374 | final double segmentEndOffset = segmentStartOffset + offset; 375 | 376 | final double segmentEndX = lineStart.x + (segmentEndOffset * cos(alpha)) + (lineOffset * cos(alpha)); 377 | final double segmentEndY = lineStart.y + (segmentEndOffset * sin(alpha)) + (lineOffset * sin(alpha)); 378 | final PointD gapEnd = PointD(segmentEndX, segmentEndY); 379 | 380 | ops.addAll(OpsGenerator.doubleLine(gapStart.x, gapStart.y, gapEnd.x, gapEnd.y, config.drawConfig)); 381 | } 382 | } 383 | return ops; 384 | } 385 | } 386 | 387 | class DotFiller extends Filler { 388 | DotFiller([FillerConfig config]) : super(config); 389 | 390 | @override 391 | OpSet fill(List points) { 392 | final FillerConfig dotConfig = _config.copyWith( 393 | drawConfig: _config.drawConfig.copyWith(curveStepCount: 4, roughness: 1), 394 | hachureAngle: 1, 395 | ); 396 | final List lines = buildFillLines(points, dotConfig); 397 | return dotsOnLines(lines, dotConfig); 398 | } 399 | 400 | OpSet dotsOnLines(List lines, FillerConfig config) { 401 | final List ops = []; 402 | final double gap = max(config.hachureGap, 0.1); 403 | final double fWeight = max(config.fillWeight, 0.1); 404 | final double ro = gap / 4; 405 | for (final Line line in lines) { 406 | final double length = line.length; 407 | final double dl = length / gap; 408 | final int count = dl.ceil() - 1; 409 | final double off = length - (count * gap); 410 | final double x = ((line.source.x + line.target.x) / 2) - (gap / 4); 411 | final double minY = min(line.source.y, line.target.y); 412 | for (int i = 0; i < count; i++) { 413 | final double y = minY + off + (i * gap); 414 | final double cx = config.drawConfig.offset(x - ro, x + ro); 415 | final double cy = config.drawConfig.offset(y - ro, y + ro); 416 | final OpSet el = OpSetBuilder.ellipse(cx, cy, fWeight, fWeight, config.drawConfig); 417 | ops.addAll(el.ops); 418 | } 419 | } 420 | return OpSet(type: OpSetType.fillSketch, ops: ops); 421 | } 422 | } 423 | 424 | class SolidFiller extends Filler { 425 | SolidFiller([FillerConfig config]) : super(config); 426 | 427 | @override 428 | OpSet fill(List points) { 429 | List result = []; 430 | if (points.length > 2) { 431 | result = points 432 | .map((point) => PointD( 433 | point.x + _config.drawConfig.offsetSymmetric(_config.fillWeight), 434 | point.y + _config.drawConfig.offsetSymmetric(_config.fillWeight), 435 | )) 436 | .toList(); 437 | result..add(result.first)..add(result[1])..add(result[2]); 438 | } 439 | return OpSet(type: OpSetType.fillPath, ops: OpsGenerator.curve(result, _config.drawConfig)); 440 | } 441 | } 442 | -------------------------------------------------------------------------------- /lib/src/generator.dart: -------------------------------------------------------------------------------- 1 | import 'config.dart'; 2 | import 'core.dart'; 3 | import 'entities.dart'; 4 | import 'filler.dart'; 5 | import 'geometry.dart'; 6 | import 'renderer.dart'; 7 | 8 | /// [Generator] is class that lets you create a [Drawable] object for a shape. 9 | class Generator { 10 | final DrawConfig drawConfig; 11 | final Filler filler; 12 | 13 | Generator(this.drawConfig, this.filler) 14 | : assert(drawConfig != null), 15 | assert(filler != null); 16 | 17 | Drawable _buildDrawable(OpSet drawSets, [List fillPoints]) { 18 | final List sets = []; 19 | if (fillPoints != null) { 20 | sets.add(filler.fill(fillPoints)); 21 | } 22 | sets.add(drawSets); 23 | return Drawable(sets: sets, options: drawConfig); 24 | } 25 | 26 | /// Draws a line from ([x1], [y1]) to ([x2], [y2]). 27 | Drawable line(double x1, double y1, double x2, double y2) { 28 | return _buildDrawable(OpSetBuilder.buildLine(x1, y1, x2, y2, drawConfig)); 29 | } 30 | 31 | ///Draws a rectangle with the top-left corner at ([x], [y]) with the specified [width] and [height]. 32 | Drawable rectangle(double x, double y, double width, double height) { 33 | final List points = [PointD(x, y), PointD(x + width, y), PointD(x + width, y + height), PointD(x, y + height)]; 34 | final OpSet outline = OpSetBuilder.buildPolygon(points, drawConfig); 35 | return _buildDrawable(outline, points); 36 | } 37 | 38 | ///Draws a rectangle with the center at ([x], [y]) with the specified [width] and [height]. 39 | Drawable ellipse(double x, double y, double width, double height) { 40 | final EllipseParams ellipseParams = generateEllipseParams(width, height, drawConfig); 41 | final OpSet ellipse = ellipseSet(x, y, drawConfig, ellipseParams); 42 | final List estimatedPoints = computeEllipseAllPoints( 43 | increment: ellipseParams.increment, 44 | cx: x, 45 | cy: y, 46 | rx: ellipseParams.rx, 47 | ry: ellipseParams.ry, 48 | offset: 0, 49 | overlap: 0, 50 | config: drawConfig, 51 | ); //ellipseEstimated(x, y, drawConfig, ellipseParams); 52 | return _buildDrawable(ellipse, estimatedPoints); 53 | } 54 | 55 | ///Draws a rectangle with the center at ([x], [y]) with the specified [diameter]. 56 | Drawable circle(double x, double y, double diameter) { 57 | final Drawable ret = ellipse(x, y, diameter, diameter); 58 | return ret; 59 | } 60 | 61 | /// Draws a set of lines connecting the specified points. 62 | // 63 | // * [points] is an array of [PointD] 64 | Drawable linearPath(List points) { 65 | return _buildDrawable(OpSetBuilder.linearPath(points, true, drawConfig)); 66 | } 67 | 68 | /// Draws a polygon with the specified vertices. 69 | /// 70 | /// * [points] is an array of [PointD] 71 | Drawable polygon(List points) { 72 | final OpSet path = OpSetBuilder.linearPath(points, true, drawConfig); 73 | return _buildDrawable(path, points); 74 | } 75 | 76 | ///Draws an arc. An arc is described as a section of an ellipse. 77 | /// 78 | /// * [x], [y] represents the center of that ellipse 79 | /// * [width], [height] are the dimensions of that ellipse. 80 | /// * [start], [stop] are the start and stop angles for the arc. 81 | /// * [closed] is a boolean argument. If true, lines are drawn to connect the two end points of the arc to the center. 82 | Drawable arc(double x, double y, double width, double height, double start, double stop, [bool closed = false]) { 83 | final OpSet outline = OpSetBuilder.arc(PointD(x, y), width, height, start, stop, closed, true, drawConfig); 84 | final List fillPoints = OpSetBuilder.arcPolygon(PointD(x, y), width, height, start, stop, drawConfig); 85 | return _buildDrawable(outline, fillPoints); 86 | } 87 | 88 | ///Draws a curve passing through the points passed in. 89 | /// 90 | /// * [points] is an array of [PointD] 91 | Drawable curvePath(List points) { 92 | return _buildDrawable(OpSetBuilder.curve(points, drawConfig)); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lib/src/geometry.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'core.dart'; 4 | import 'entities.dart'; 5 | 6 | List rotatePoints(List points, PointD center, double degrees) { 7 | if (points != null && points.isNotEmpty) { 8 | return points.map((p) => rotatePoint(p, center, degrees)).toList(); 9 | } else { 10 | return []; 11 | } 12 | } 13 | 14 | PointD rotatePoint(PointD point, PointD center, double degrees) { 15 | final double angle = (pi / 180) * degrees; 16 | final double angleCos = cos(angle); 17 | final double angleSin = sin(angle); 18 | 19 | return PointD( 20 | ((point.x - center.x) * angleCos) - ((point.y - center.y) * angleSin) + center.x, 21 | ((point.x - center.x) * angleSin) + ((point.y - center.y) * angleCos) + center.y, 22 | ); 23 | } 24 | 25 | List rotateLines(List lines, PointD center, double degrees) => 26 | lines.map((line) => Line(rotatePoint(line.source, center, degrees), rotatePoint(line.target, center, degrees))).toList(); 27 | 28 | enum PointsOrientation { collinear, clockwise, counterclockwise } 29 | 30 | PointsOrientation getOrientation(PointD p, PointD q, PointD r) { 31 | final double val = (q.x - p.x) * (r.y - q.y) - (q.y - p.y) * (r.x - q.x); 32 | if (val == 0) { 33 | return PointsOrientation.collinear; 34 | } 35 | return val > 0 ? PointsOrientation.clockwise : PointsOrientation.counterclockwise; 36 | } 37 | 38 | bool onSegmentPoints(PointD source, PointD point, PointD target) => Line(source, target).onSegment(point); 39 | 40 | class ComputedEllipsePoints { 41 | List corePoints; 42 | List allPoints; 43 | 44 | ComputedEllipsePoints({this.corePoints, this.allPoints}); 45 | } 46 | 47 | class EllipseParams { 48 | final double rx; 49 | final double ry; 50 | final double increment; 51 | 52 | EllipseParams({this.rx, this.ry, this.increment}); 53 | } 54 | 55 | class EllipseResult { 56 | OpSet opSet; 57 | List estimatedPoints; 58 | 59 | EllipseResult({this.opSet, this.estimatedPoints}); 60 | } 61 | 62 | class Edge { 63 | double yMin; 64 | double yMax; 65 | double x; 66 | double slope; 67 | 68 | Edge({this.yMin, this.yMax, this.x, this.slope}); 69 | 70 | Edge copyWith({double yMin, double yMax, double x, double slope}) => Edge( 71 | yMin: yMin ?? this.yMin, 72 | yMax: yMax ?? this.yMax, 73 | x: x ?? this.x, 74 | slope: slope ?? this.slope, 75 | ); 76 | 77 | @override 78 | String toString() { 79 | return 'Edge{yMin: $yMin, yMax: $yMax, x: $x, isLope: $slope}'; 80 | } 81 | } 82 | 83 | class ActiveEdge { 84 | double s; 85 | Edge edge; 86 | 87 | ActiveEdge(this.s, this.edge); 88 | } 89 | -------------------------------------------------------------------------------- /lib/src/renderer.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'config.dart'; 4 | import 'core.dart'; 5 | import 'entities.dart'; 6 | import 'geometry.dart'; 7 | 8 | List _line(double x1, double y1, double x2, double y2, DrawConfig config, bool move, bool overlay) { 9 | final lengthSq = pow(x1 - x2, 2) + pow(y1 - y2, 2); 10 | final length = sqrt(lengthSq); 11 | double roughnessGain; 12 | 13 | if (length < 200) { 14 | roughnessGain = 1; 15 | } else if (length > 500) { 16 | roughnessGain = 0.4; 17 | } else { 18 | roughnessGain = (-0.0016668) * length + 1.233334; 19 | } 20 | 21 | double offset = config.maxRandomnessOffset; 22 | if ((offset * offset * 100) > lengthSq) { 23 | offset = length / 10; 24 | } 25 | 26 | final halfOffset = offset / 2; 27 | final divergePoint = 0.2 + config.randomizer.next() * 0.2; 28 | 29 | double offsetX = config.bowing * config.maxRandomnessOffset * (y2 - y1) / 200; 30 | double offsetY = config.bowing * config.maxRandomnessOffset * (x1 - x2) / 200; 31 | offsetX = config.offsetSymmetric(offsetX, roughnessGain); 32 | offsetY = config.offsetSymmetric(offsetY, roughnessGain); 33 | 34 | final ops = []; 35 | final randomHalf = () => config.offsetSymmetric(halfOffset, roughnessGain); 36 | final randomFull = () => config.offsetSymmetric(offset, roughnessGain); 37 | 38 | if (move) { 39 | if (overlay) { 40 | ops.add(Op.move(PointD(x1 + randomHalf(), y1 + randomHalf()))); 41 | } else { 42 | ops.add(Op.move(PointD(x1 + config.offsetSymmetric(offset, roughnessGain), y1 + config.offsetSymmetric(offset, roughnessGain)))); 43 | } 44 | } 45 | if (overlay) { 46 | ops.add( 47 | Op.curveTo( 48 | PointD( 49 | offsetX + x1 + (x2 - x1) * divergePoint + randomHalf(), 50 | offsetY + y1 + (y2 - y1) * divergePoint + randomHalf(), 51 | ), 52 | PointD( 53 | offsetX + x1 + 2 * (x2 - x1) * divergePoint + randomHalf(), 54 | offsetY + y1 + 2 * (y2 - y1) * divergePoint + randomHalf(), 55 | ), 56 | PointD( 57 | x2 + randomHalf(), 58 | y2 + randomHalf(), 59 | ), 60 | ), 61 | ); 62 | } else { 63 | ops.add( 64 | Op.curveTo( 65 | PointD( 66 | offsetX + x1 + (x2 - x1) * divergePoint + randomFull(), 67 | offsetY + y1 + (y2 - y1) * divergePoint + randomFull(), 68 | ), 69 | PointD( 70 | offsetX + x1 + 2 * (x2 - x1) * divergePoint + randomFull(), 71 | offsetY + y1 + 2 * (y2 - y1) * divergePoint + randomFull(), 72 | ), 73 | PointD( 74 | x2 + randomFull(), 75 | y2 + randomFull(), 76 | ), 77 | ), 78 | ); 79 | } 80 | return ops; 81 | } 82 | 83 | class OpSetBuilder { 84 | static OpSet buildLine(double x1, double y1, double x2, double y2, DrawConfig config) { 85 | return OpSet(type: OpSetType.path, ops: OpsGenerator.doubleLine(x1, y1, x2, y2, config)); 86 | } 87 | 88 | static OpSet ellipse(double x, double y, double width, double height, DrawConfig config) { 89 | final EllipseParams params = generateEllipseParams(width, height, config); 90 | return ellipseSet(x, y, config, params); 91 | } 92 | 93 | static OpSet buildPolygon(List points, DrawConfig config) { 94 | return linearPath(points, true, config); 95 | } 96 | 97 | static OpSet linearPath(List points, bool close, DrawConfig config) { 98 | final int len = (points ?? []).length; 99 | if (len > 2) { 100 | List ops = []; 101 | for (int i = 0; i < len - 1; i++) { 102 | ops += OpsGenerator.doubleLine(points[i].x, points[i].y, points[i + 1].x, points[i + 1].y, config); 103 | } 104 | if (close) { 105 | ops += OpsGenerator.doubleLine(points[len - 1].x, points[len - 1].y, points[0].x, points[0].y, config); 106 | } 107 | return OpSet(type: OpSetType.path, ops: ops); 108 | } else if (len == 2) { 109 | return buildLine(points[0].x, points[0].x, points[1].x, points[1].x, config); 110 | } else { 111 | return OpSet(type: OpSetType.path, ops: []); 112 | } 113 | } 114 | 115 | static OpSet arc( 116 | PointD center, double width, double height, double start, double stop, bool closed, bool roughClosure, DrawConfig config) { 117 | final List ops = []; 118 | final double cx = center.x; 119 | final double cy = center.y; 120 | double rx = (width / 2).abs(); 121 | double ry = (height / 2).abs(); 122 | rx += config.offsetSymmetric(rx * 0.01); 123 | ry += config.offsetSymmetric(ry * 0.01); 124 | double strt = start; 125 | double stp = stop; 126 | while (strt < 0) { 127 | strt += pi * 2; 128 | stp += pi * 2; 129 | } 130 | if ((stp - strt) > (pi * 2)) { 131 | strt = 0; 132 | stp = pi * 2; 133 | } 134 | final double ellipseInc = pi * 2 / config.curveStepCount; 135 | final double arcIn = min(ellipseInc / 2, (stp - strt) / 2); 136 | ops 137 | ..addAll(OpsGenerator.arc(arcIn, cx, cy, rx, ry, strt, stp, 1, config)) 138 | ..addAll(OpsGenerator.arc(arcIn, cx, cy, rx, ry, strt, stp, 1.5, config)); 139 | if (closed) { 140 | if (roughClosure) { 141 | ops 142 | ..addAll(OpsGenerator.doubleLine(cx, cy, cx + rx * cos(strt), cy + ry * sin(strt), config)) 143 | ..addAll(OpsGenerator.doubleLine(cx, cy, cx + rx * cos(stp), cy + ry * sin(stp), config)); 144 | } else { 145 | ops..add(Op.lineTo(PointD(cx, cy)))..add(Op.lineTo(PointD(cx + rx * cos(strt), cy + ry * sin(strt)))); 146 | } 147 | } 148 | return OpSet(type: OpSetType.path, ops: ops); 149 | } 150 | 151 | static List arcPolygon(PointD center, double width, double height, double startAngle, double stopAngle, DrawConfig config) { 152 | double radiusX = (width / 2).abs(); 153 | double radiusY = (height / 2).abs(); 154 | radiusX += config.offsetSymmetric(radiusX * 0.01); 155 | radiusY += config.offsetSymmetric(radiusY * 0.01); 156 | double start = startAngle; 157 | double stop = stopAngle; 158 | while (start < 0) { 159 | start += pi * 2; 160 | stop += pi * 2; 161 | } 162 | if ((stop - start) > (pi * 2)) { 163 | start = 0; 164 | stop = pi * 2; 165 | } 166 | final double ellipseInc = pi * 2 / config.curveStepCount; 167 | final double increment = min(ellipseInc / 2, (stop - start) / 2); 168 | //final double increment = (stop - start) / (config.curveStepCount * 2); 169 | final List points = []; 170 | for (double angle = start; angle <= stop; angle = angle + increment) { 171 | points.add(PointD(center.x + radiusX * cos(angle), center.y + radiusY * sin(angle))); 172 | } 173 | points..add(PointD(center.x + radiusX * cos(stop), center.y + radiusY * sin(stop)))..add(center); 174 | return points; 175 | } 176 | 177 | static OpSet curve(List points, DrawConfig config) { 178 | final List op1 = OpsGenerator.curveWithOffset(points, 1 * (1 + config.roughness * 0.2), config); 179 | final List op2 = OpsGenerator.curveWithOffset(points, 1.5 * (1 + config.roughness * 0.2), config); 180 | return OpSet(type: OpSetType.path, ops: op1 + op2); 181 | } 182 | } 183 | 184 | class OpsGenerator { 185 | static List doubleLine(double x1, double y1, double x2, double y2, DrawConfig config) { 186 | final List o1 = _line(x1, y1, x2, y2, config, true, false); 187 | final List o2 = _line(x1, y1, x2, y2, config, true, true); 188 | return o1 + o2; 189 | } 190 | 191 | static List curve(List points, DrawConfig config) { 192 | final int len = points.length; 193 | if (len > 3) { 194 | final List ops = []; 195 | final double s = 1 - config.curveTightness; 196 | ops.add(Op.move(points[1])); 197 | for (int i = 1; (i + 2) < len; i++) { 198 | final point = points[i]; 199 | final next = points[i + 1]; 200 | final afterNext = points[i + 2]; 201 | final PointD previous = points[i - 1]; 202 | final control1 = PointD(point.x + (s * next.x - s * previous.x) / 6, point.y + (s * next.y - s * previous.y) / 6); 203 | final control2 = PointD(next.x + (s * point.x - s * afterNext.x) / 6, next.y + (s * point.y - s * afterNext.y) / 6); 204 | final end = PointD(next.x, next.y); 205 | ops.add(Op.curveTo(control1, control2, end)); 206 | } 207 | return ops; 208 | } else if (len == 3) { 209 | return [Op.move(points[1]), Op.curveTo(points[1], points[2], points[2])]; 210 | } else if (len == 2) { 211 | return doubleLine(points[0].x, points[0].y, points[1].x, points[1].y, config); 212 | } 213 | return []; 214 | } 215 | 216 | static List curveWithOffset(List points, double offset, DrawConfig config) { 217 | final List result = [ 218 | PointD( 219 | points.first.x + config.offsetSymmetric(offset), 220 | points.first.y + config.offsetSymmetric(offset), 221 | ), 222 | ...points.map((point) => PointD( 223 | point.x + config.offsetSymmetric(offset), 224 | point.y + config.offsetSymmetric(offset), 225 | )), 226 | PointD( 227 | points.last.x + config.offsetSymmetric(offset), 228 | points.last.y + config.offsetSymmetric(offset), 229 | ) 230 | ]; 231 | return curve(result, config); 232 | } 233 | 234 | static List arc( 235 | double increment, double cx, double cy, double rx, double ry, double strt, double stp, double offset, DrawConfig config) { 236 | final List points = []; 237 | final double radOffset = strt + config.offsetSymmetric(0.1); 238 | points.add(PointD( 239 | config.offsetSymmetric(offset) + cx + 0.9 * rx * cos(radOffset - increment), 240 | config.offsetSymmetric(offset) + cy + 0.9 * ry * sin(radOffset - increment), 241 | )); 242 | for (double angle = radOffset; angle <= stp; angle += increment) { 243 | points.add(PointD( 244 | config.offsetSymmetric(offset) + cx + rx * cos(angle), 245 | config.offsetSymmetric(offset) + cy + ry * sin(angle), 246 | )); 247 | } 248 | points..add(PointD(cx + rx * cos(stp), cy + ry * sin(stp)))..add(PointD(cx + rx * cos(stp), cy + ry * sin(stp))); 249 | return curve(points, config); 250 | } 251 | } 252 | 253 | EllipseParams generateEllipseParams(double width, double height, DrawConfig config) { 254 | final double psq = sqrt(pi * 2 * sqrt((pow(width / 2, 2) + pow(height / 2, 2)) / 2)); 255 | final double stepCount = max(config.curveStepCount, (config.curveStepCount / sqrt(200)) * psq); 256 | final double increment = (pi * 2) / stepCount; 257 | final double curveFitRandomness = 1 - config.curveFitting; 258 | 259 | double rx = (width / 2).abs(); 260 | double ry = (height / 2).abs(); 261 | rx += config.offsetSymmetric(rx * curveFitRandomness); 262 | ry += config.offsetSymmetric(ry * curveFitRandomness); 263 | 264 | return EllipseParams(increment: increment, rx: rx, ry: ry); 265 | } 266 | 267 | EllipseResult ellipseWithParamsx(double x, double y, DrawConfig config, EllipseParams ellipseParams) { 268 | final ComputedEllipsePoints ellipsePoints1 = _computeEllipsePoints( 269 | increment: ellipseParams.increment, 270 | cx: x, 271 | cy: y, 272 | rx: ellipseParams.rx, 273 | ry: ellipseParams.ry, 274 | offset: 1, 275 | overlap: ellipseParams.increment * config.offset(0.1, config.offset(0.4, 1)), 276 | config: config, 277 | ); 278 | final ComputedEllipsePoints ellipsePoints2 = _computeEllipsePoints( 279 | increment: ellipseParams.increment, 280 | cx: x, 281 | cy: y, 282 | rx: ellipseParams.rx, 283 | ry: ellipseParams.ry, 284 | offset: 1.5, 285 | overlap: 0, 286 | config: config, 287 | ); 288 | final List o1 = OpsGenerator.curve(ellipsePoints1.allPoints, config); 289 | final List o2 = OpsGenerator.curve(ellipsePoints2.allPoints, config); 290 | return EllipseResult(estimatedPoints: ellipsePoints1.corePoints, opSet: OpSet(type: OpSetType.path, ops: o1 + o2)); 291 | } 292 | 293 | ComputedEllipsePoints _computeEllipsePoints({ 294 | double increment, 295 | double cx, 296 | double cy, 297 | double rx, 298 | double ry, 299 | double offset, 300 | double overlap, 301 | DrawConfig config, 302 | }) { 303 | final List corePoints = []; 304 | final List allPoints = []; 305 | final double radOffset = config.offsetSymmetric(0.5) - pi / 2; 306 | allPoints.add(PointD( 307 | config.offsetSymmetric(offset) + cx + 0.9 * rx * cos(radOffset - increment), 308 | config.offsetSymmetric(offset) + cy + 0.9 * ry * sin(radOffset - increment), 309 | )); 310 | for (double angle = radOffset; angle < (pi * 2 + radOffset - 0.01); angle = angle + increment) { 311 | final PointD p = PointD( 312 | config.offsetSymmetric(offset) + cx + rx * cos(angle), 313 | config.offsetSymmetric(offset) + cy + ry * sin(angle), 314 | ); 315 | allPoints.add(p); 316 | corePoints.add(p); 317 | } 318 | allPoints 319 | ..add(PointD( 320 | config.offsetSymmetric(offset) + cx + rx * cos(radOffset + pi * 2 + overlap * 0.5), 321 | config.offsetSymmetric(offset) + cy + ry * sin(radOffset + pi * 2 + overlap * 0.5), 322 | )) 323 | ..add(PointD( 324 | config.offsetSymmetric(offset) + cx + 0.98 * rx * cos(radOffset + overlap), 325 | config.offsetSymmetric(offset) + cy + 0.98 * ry * sin(radOffset + overlap), 326 | )) 327 | ..add(PointD( 328 | config.offsetSymmetric(offset) + cx + 0.9 * rx * cos(radOffset + overlap * 0.5), 329 | config.offsetSymmetric(offset) + cy + 0.9 * ry * sin(radOffset + overlap * 0.5), 330 | )); 331 | return ComputedEllipsePoints(corePoints: corePoints, allPoints: allPoints); 332 | } 333 | 334 | OpSet ellipseSet(double x, double y, DrawConfig config, EllipseParams ellipseParams) { 335 | final List ellipsePoints1 = computeEllipseAllPoints( 336 | increment: ellipseParams.increment, 337 | cx: x, 338 | cy: y, 339 | rx: ellipseParams.rx, 340 | ry: ellipseParams.ry, 341 | offset: 1, 342 | overlap: ellipseParams.increment * config.offset(0.1, config.offset(0.4, 1)), 343 | config: config, 344 | ); 345 | final List ellipsePoints2 = computeEllipseAllPoints( 346 | increment: ellipseParams.increment, 347 | cx: x, 348 | cy: y, 349 | rx: ellipseParams.rx, 350 | ry: ellipseParams.ry, 351 | offset: 1.5, 352 | overlap: 0, 353 | config: config, 354 | ); 355 | final List o1 = OpsGenerator.curve(ellipsePoints1, config); 356 | final List o2 = OpsGenerator.curve(ellipsePoints2, config); 357 | return OpSet(type: OpSetType.path, ops: o1 + o2); 358 | } 359 | 360 | List ellipseEstimated(double x, double y, DrawConfig config, EllipseParams ellipseParams) { 361 | final List corePoints = []; 362 | final double radOffset = config.offsetSymmetric(0.5) - pi / 2; 363 | for (double angle = radOffset; angle < (pi * 2 + radOffset - 0.01); angle = angle + ellipseParams.increment) { 364 | final PointD p = PointD( 365 | config.offsetSymmetric(1) + x + ellipseParams.rx * cos(angle), 366 | config.offsetSymmetric(1) + y + ellipseParams.ry * sin(angle), 367 | ); 368 | corePoints.add(p); 369 | } 370 | return corePoints; 371 | } 372 | 373 | List computeEllipseAllPoints({ 374 | double increment, 375 | double cx, 376 | double cy, 377 | double rx, 378 | double ry, 379 | double offset, 380 | double overlap, 381 | DrawConfig config, 382 | }) { 383 | final List allPoints = []; 384 | final double radOffset = config.offsetSymmetric(0.5) - pi / 2; 385 | allPoints.add(PointD( 386 | config.offsetSymmetric(offset) + cx + 0.9 * rx * cos(radOffset - increment), 387 | config.offsetSymmetric(offset) + cy + 0.9 * ry * sin(radOffset - increment), 388 | )); 389 | for (double angle = radOffset; angle < (pi * 2 + radOffset - 0.01); angle = angle + increment) { 390 | allPoints.add(PointD( 391 | config.offsetSymmetric(offset) + cx + rx * cos(angle), 392 | config.offsetSymmetric(offset) + cy + ry * sin(angle), 393 | )); 394 | } 395 | allPoints 396 | ..add(PointD( 397 | config.offsetSymmetric(offset) + cx + rx * cos(radOffset + pi * 2 + overlap * 0.5), 398 | config.offsetSymmetric(offset) + cy + ry * sin(radOffset + pi * 2 + overlap * 0.5), 399 | )) 400 | ..add(PointD( 401 | config.offsetSymmetric(offset) + cx + 0.98 * rx * cos(radOffset + overlap), 402 | config.offsetSymmetric(offset) + cy + 0.98 * ry * sin(radOffset + overlap), 403 | )) 404 | ..add(PointD( 405 | config.offsetSymmetric(offset) + cx + 0.9 * rx * cos(radOffset + overlap * 0.5), 406 | config.offsetSymmetric(offset) + cy + 0.9 * ry * sin(radOffset + overlap * 0.5), 407 | )); 408 | return allPoints; 409 | } 410 | -------------------------------------------------------------------------------- /lib/src/rough.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'core.dart'; 5 | import 'entities.dart'; 6 | 7 | /// This is the base Rough class for painting 8 | extension Rough on Canvas { 9 | Path _drawToContext(OpSet drawing) { 10 | final Path path = Path(); 11 | for (final Op op in drawing.ops) { 12 | final data = op.data; 13 | switch (op.op) { 14 | case OpType.move: 15 | path.moveTo(data[0].x, data[0].y); 16 | break; 17 | case OpType.curveTo: 18 | path.cubicTo(data[0].x, data[0].y, data[1].x, data[1].y, data[2].x, data[2].y); 19 | break; 20 | case OpType.lineTo: 21 | path.lineTo(data[0].x, data[0].y); 22 | break; 23 | } 24 | } 25 | return path; 26 | } 27 | 28 | /// Draws a rough Drawable 29 | /// 30 | /// 31 | void drawRough(Drawable drawable, Paint pathPaint, Paint fillPaint) { 32 | for (final OpSet drawing in drawable.sets ?? []) { 33 | switch (drawing.type) { 34 | case OpSetType.path: 35 | drawPath(_drawToContext(drawing), pathPaint); 36 | 37 | break; 38 | case OpSetType.fillPath: 39 | final Paint _fillPaint = fillPaint; 40 | _fillPaint.style = PaintingStyle.fill; 41 | final Path _path = _drawToContext(drawing)..close(); 42 | drawPath(_path, _fillPaint); 43 | break; 44 | case OpSetType.fillSketch: 45 | drawPath(_drawToContext(drawing), fillPaint); 46 | break; 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | archive: 5 | dependency: transitive 6 | description: 7 | name: archive 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.0.13" 11 | args: 12 | dependency: transitive 13 | description: 14 | name: args 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "1.6.0" 18 | async: 19 | dependency: transitive 20 | description: 21 | name: async 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.4.1" 25 | boolean_selector: 26 | dependency: transitive 27 | description: 28 | name: boolean_selector 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "2.0.0" 32 | charcode: 33 | dependency: transitive 34 | description: 35 | name: charcode 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.3" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.14.12" 46 | convert: 47 | dependency: transitive 48 | description: 49 | name: convert 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "2.1.1" 53 | crypto: 54 | dependency: transitive 55 | description: 56 | name: crypto 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "2.1.4" 60 | extra_pedantic: 61 | dependency: "direct dev" 62 | description: 63 | name: extra_pedantic 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "1.2.0" 67 | flutter: 68 | dependency: "direct main" 69 | description: flutter 70 | source: sdk 71 | version: "0.0.0" 72 | flutter_test: 73 | dependency: "direct dev" 74 | description: flutter 75 | source: sdk 76 | version: "0.0.0" 77 | image: 78 | dependency: transitive 79 | description: 80 | name: image 81 | url: "https://pub.dartlang.org" 82 | source: hosted 83 | version: "2.1.12" 84 | matcher: 85 | dependency: transitive 86 | description: 87 | name: matcher 88 | url: "https://pub.dartlang.org" 89 | source: hosted 90 | version: "0.12.6" 91 | meta: 92 | dependency: transitive 93 | description: 94 | name: meta 95 | url: "https://pub.dartlang.org" 96 | source: hosted 97 | version: "1.1.8" 98 | path: 99 | dependency: transitive 100 | description: 101 | name: path 102 | url: "https://pub.dartlang.org" 103 | source: hosted 104 | version: "1.6.4" 105 | petitparser: 106 | dependency: transitive 107 | description: 108 | name: petitparser 109 | url: "https://pub.dartlang.org" 110 | source: hosted 111 | version: "2.4.0" 112 | quiver: 113 | dependency: transitive 114 | description: 115 | name: quiver 116 | url: "https://pub.dartlang.org" 117 | source: hosted 118 | version: "2.1.3" 119 | sky_engine: 120 | dependency: transitive 121 | description: flutter 122 | source: sdk 123 | version: "0.0.99" 124 | source_span: 125 | dependency: transitive 126 | description: 127 | name: source_span 128 | url: "https://pub.dartlang.org" 129 | source: hosted 130 | version: "1.7.0" 131 | stack_trace: 132 | dependency: transitive 133 | description: 134 | name: stack_trace 135 | url: "https://pub.dartlang.org" 136 | source: hosted 137 | version: "1.9.3" 138 | stream_channel: 139 | dependency: transitive 140 | description: 141 | name: stream_channel 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "2.0.0" 145 | string_scanner: 146 | dependency: transitive 147 | description: 148 | name: string_scanner 149 | url: "https://pub.dartlang.org" 150 | source: hosted 151 | version: "1.0.5" 152 | term_glyph: 153 | dependency: transitive 154 | description: 155 | name: term_glyph 156 | url: "https://pub.dartlang.org" 157 | source: hosted 158 | version: "1.1.0" 159 | test_api: 160 | dependency: transitive 161 | description: 162 | name: test_api 163 | url: "https://pub.dartlang.org" 164 | source: hosted 165 | version: "0.2.15" 166 | typed_data: 167 | dependency: transitive 168 | description: 169 | name: typed_data 170 | url: "https://pub.dartlang.org" 171 | source: hosted 172 | version: "1.1.6" 173 | vector_math: 174 | dependency: transitive 175 | description: 176 | name: vector_math 177 | url: "https://pub.dartlang.org" 178 | source: hosted 179 | version: "2.0.8" 180 | xml: 181 | dependency: transitive 182 | description: 183 | name: xml 184 | url: "https://pub.dartlang.org" 185 | source: hosted 186 | version: "3.6.1" 187 | sdks: 188 | dart: ">=2.6.0 <3.0.0" 189 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: rough 2 | description: Rough is a library that allows you draw in a sketchy, hand-drawn-like style 3 | version: 0.1.1 4 | homepage: https://github.com/sergiandreplace/flutter_rough 5 | repository: https://github.com/sergiandreplace/flutter_rough 6 | 7 | environment: 8 | sdk: ">=2.6.0 <3.0.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | dev_dependencies: 15 | flutter_test: 16 | sdk: flutter 17 | extra_pedantic: 1.2.0 18 | 19 | flutter: 20 | -------------------------------------------------------------------------------- /screenshots/circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiandreplace/flutter_rough/4ed0da47b52cb4d3aa7c38d088f3f4c5a19d4aeb/screenshots/circle.png -------------------------------------------------------------------------------- /screenshots/example_app_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiandreplace/flutter_rough/4ed0da47b52cb4d3aa7c38d088f3f4c5a19d4aeb/screenshots/example_app_1.jpg -------------------------------------------------------------------------------- /screenshots/example_app_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiandreplace/flutter_rough/4ed0da47b52cb4d3aa7c38d088f3f4c5a19d4aeb/screenshots/example_app_2.jpg -------------------------------------------------------------------------------- /screenshots/example_app_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiandreplace/flutter_rough/4ed0da47b52cb4d3aa7c38d088f3f4c5a19d4aeb/screenshots/example_app_3.jpg -------------------------------------------------------------------------------- /screenshots/example_app_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiandreplace/flutter_rough/4ed0da47b52cb4d3aa7c38d088f3f4c5a19d4aeb/screenshots/example_app_4.jpg -------------------------------------------------------------------------------- /scripts/cache.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | rm $FLUTTER_ROOT/.pub-cache/credentials.json 2> /dev/null 3 | echo $CACHED_JSON > $FLUTTER_ROOT/.pub-cache/credentials.json 4 | 5 | -------------------------------------------------------------------------------- /test/rough_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | void main() { 4 | test('adds one to input values', () { 5 | // final calculator = Calculator(); 6 | // expect(calculator.addOne(2), 3); 7 | // expect(calculator.addOne(-7), -6); 8 | // expect(calculator.addOne(0), 1); 9 | // expect(() => calculator.addOne(null), throwsNoSuchMethodError); 10 | }); 11 | } 12 | --------------------------------------------------------------------------------