├── .env.empty
├── .gitignore
├── .metadata
├── .vscode
├── launch.json
└── settings.json
├── LICENSE
├── README.md
├── README_DEVELOP.md
├── TODO.md
├── analysis_options.yaml
├── android
├── .gitignore
├── app
│ ├── build.gradle
│ └── src
│ │ ├── debug
│ │ └── AndroidManifest.xml
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── kotlin
│ │ │ └── com
│ │ │ │ └── example
│ │ │ │ └── flutter_puzzle
│ │ │ │ └── MainActivity.kt
│ │ └── res
│ │ │ ├── drawable-v21
│ │ │ └── launch_background.xml
│ │ │ ├── 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-night
│ │ │ └── styles.xml
│ │ │ └── values
│ │ │ └── styles.xml
│ │ └── profile
│ │ └── AndroidManifest.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
└── settings.gradle
├── assets
├── audio
│ ├── music
│ │ ├── bensound-extremeaction.mp3
│ │ ├── bensound-punky.mp3
│ │ └── bensound-scifi.mp3
│ └── sounds
│ │ ├── awesome.mp3
│ │ ├── beep.mp3
│ │ ├── beep_long.mp3
│ │ ├── click.wav
│ │ ├── excellent.mp3
│ │ ├── game_over.mp3
│ │ ├── great.mp3
│ │ ├── keep_it_up.mp3
│ │ ├── swish.wav
│ │ ├── that_was_close.mp3
│ │ ├── too_easy.mp3
│ │ └── well_done.mp3
└── launcher
│ ├── icon.png
│ └── icon_rounded.png
├── build_web.sh
├── docs
└── private_policy.txt
├── fonts
├── AzeretMono-Black.ttf
├── AzeretMono-Bold.ttf
├── AzeretMono-ExtraBold.ttf
├── AzeretMono-SemiBold.ttf
├── Rowdies-Bold.ttf
├── Rowdies-Light.ttf
└── Rowdies-Regular.ttf
├── ios
├── .gitignore
├── Flutter
│ ├── AppFrameworkInfo.plist
│ ├── Debug.xcconfig
│ └── Release.xcconfig
├── Podfile
├── Podfile.lock
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── WorkspaceSettings.xcsettings
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
└── 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-50x50@1x.png
│ │ ├── Icon-App-50x50@2x.png
│ │ ├── Icon-App-57x57@1x.png
│ │ ├── Icon-App-57x57@2x.png
│ │ ├── Icon-App-60x60@2x.png
│ │ ├── Icon-App-60x60@3x.png
│ │ ├── Icon-App-72x72@1x.png
│ │ ├── Icon-App-72x72@2x.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
├── app_lifecycle.dart
├── audio.dart
├── audio_dialog.dart
├── background.dart
├── build_context_extension.dart
├── config.dart
├── env.dart
├── game
│ ├── countdown.dart
│ ├── game.dart
│ ├── game_time.dart
│ ├── grid.dart
│ ├── puzzle.dart
│ ├── save_game.dart
│ └── tile.dart
├── highscore_dialog.dart
├── leaderboard.dart
├── leaderboard_dialog.dart
├── lerp_value.dart
├── main.dart
├── music_credits.dart
├── scenes
│ ├── cubes.dart
│ ├── particle.dart
│ ├── rays.dart
│ ├── scene.dart
│ └── start_screen.dart
└── state_transition.dart
├── macos
├── .gitignore
├── Flutter
│ ├── Flutter-Debug.xcconfig
│ ├── Flutter-Release.xcconfig
│ └── GeneratedPluginRegistrant.swift
├── Podfile
├── Podfile.lock
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── Runner
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── app_icon_1024.png
│ │ ├── app_icon_128.png
│ │ ├── app_icon_16.png
│ │ ├── app_icon_256.png
│ │ ├── app_icon_32.png
│ │ ├── app_icon_512.png
│ │ └── app_icon_64.png
│ ├── Base.lproj
│ └── MainMenu.xib
│ ├── Configs
│ ├── AppInfo.xcconfig
│ ├── Debug.xcconfig
│ ├── Release.xcconfig
│ └── Warnings.xcconfig
│ ├── DebugProfile.entitlements
│ ├── Info.plist
│ ├── MainFlutterWindow.swift
│ └── Release.entitlements
├── pubspec.lock
├── pubspec.yaml
├── source_assets
└── loading
│ ├── loading_00.png
│ ├── loading_00_small.png
│ ├── loading_01.png
│ ├── loading_01_small.png
│ ├── loading_02.png
│ ├── loading_02_small.png
│ ├── loading_03.png
│ └── loading_03_small.png
├── test
└── widget_test.dart
├── web
├── favicon.png
├── icons
│ ├── Icon-192.png
│ ├── Icon-512.png
│ ├── Icon-maskable-192.png
│ └── Icon-maskable-512.png
├── img
│ ├── loading.gif
│ ├── loading_small.gif
│ └── splash.png
├── index.html
└── manifest.json
└── windows
├── .gitignore
├── CMakeLists.txt
├── flutter
├── CMakeLists.txt
├── generated_plugin_registrant.cc
├── generated_plugin_registrant.h
└── generated_plugins.cmake
└── runner
├── CMakeLists.txt
├── Runner.rc
├── flutter_window.cpp
├── flutter_window.h
├── main.cpp
├── resource.h
├── resources
└── app_icon.ico
├── runner.exe.manifest
├── utils.cpp
├── utils.h
├── win32_window.cpp
└── win32_window.h
/.env.empty:
--------------------------------------------------------------------------------
1 | HASH=
2 | SALT=
3 | SUPABASE_URL=
4 | SUPABASE_ANON_KEY=
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | temp/
12 |
13 | # Secrets
14 | env.g.dart
15 | .env
16 |
17 | # IntelliJ related
18 | *.iml
19 | *.ipr
20 | *.iws
21 | .idea/
22 |
23 | # The .vscode folder contains launch configuration and tasks you configure in
24 | # VS Code which you may wish to be included in version control, so this line
25 | # is commented out by default.
26 | #.vscode/
27 |
28 | # Flutter/Dart/Pub related
29 | **/doc/api/
30 | **/ios/Flutter/.last_build_id
31 | .dart_tool/
32 | .flutter-plugins
33 | .flutter-plugins-dependencies
34 | .packages
35 | .pub-cache/
36 | .pub/
37 | /build/
38 |
39 | # Web related
40 |
41 | # Symbolication related
42 | app.*.symbols
43 |
44 | # Obfuscation related
45 | app.*.map.json
46 |
47 | # Android Studio will place build artifacts here
48 | /android/app/debug
49 | /android/app/profile
50 | /android/app/release
51 |
52 | # Generated Dart files
53 | **/*.g.dart
54 |
--------------------------------------------------------------------------------
/.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.
5 |
6 | version:
7 | revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268
8 | channel: stable
9 |
10 | project_type: app
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268
17 | base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268
18 | - platform: windows
19 | create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268
20 | base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268
21 |
22 | # User provided section
23 |
24 | # List of Local paths (relative to this file) that should be
25 | # ignored by the migrate tool.
26 | #
27 | # Files that are not part of the templates will be ignored by default.
28 | unmanaged_files:
29 | - 'lib/main.dart'
30 | - 'ios/Runner.xcodeproj/project.pbxproj'
31 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "pushtrix",
9 | "request": "launch",
10 | "type": "dart"
11 | },
12 | {
13 | "name": "pushtrix (profile mode)",
14 | "request": "launch",
15 | "type": "dart",
16 | "flutterMode": "profile"
17 | }
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cmake.configureOnOpen": false
3 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Business Source License 1.1
2 |
3 | Parameters
4 |
5 | Licensor: Gottfried Chen
6 | Licensed Work: Pushtrix
7 | The Licensed Work is (c) 2022 Gottfried Chen.
8 | Additional Use Grant: None
9 |
10 | Change Date: 2026-06-04
11 |
12 | Change License: Apache License, Version 2.0
13 |
14 | Notice
15 |
16 | The Business Source License (this document, or the "License") is not an Open
17 | Source license. However, the Licensed Work will eventually be made available
18 | under an Open Source License, as stated in this License.
19 |
20 | License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved.
21 | "Business Source License" is a trademark of MariaDB Corporation Ab.
22 |
23 | -----------------------------------------------------------------------------
24 |
25 | Business Source License 1.1
26 |
27 | Terms
28 |
29 | The Licensor hereby grants you the right to copy, modify, create derivative
30 | works, redistribute, and make non-production use of the Licensed Work. The
31 | Licensor may make an Additional Use Grant, above, permitting limited
32 | production use.
33 |
34 | Effective on the Change Date, or the fourth anniversary of the first publicly
35 | available distribution of a specific version of the Licensed Work under this
36 | License, whichever comes first, the Licensor hereby grants you rights under
37 | the terms of the Change License, and the rights granted in the paragraph
38 | above terminate.
39 |
40 | If your use of the Licensed Work does not comply with the requirements
41 | currently in effect as described in this License, you must purchase a
42 | commercial license from the Licensor, its affiliated entities, or authorized
43 | resellers, or you must refrain from using the Licensed Work.
44 |
45 | All copies of the original and modified Licensed Work, and derivative works
46 | of the Licensed Work, are subject to this License. This License applies
47 | separately for each version of the Licensed Work and the Change Date may vary
48 | for each version of the Licensed Work released by Licensor.
49 |
50 | You must conspicuously display this License on each original or modified copy
51 | of the Licensed Work. If you receive the Licensed Work in original or
52 | modified form from a third party, the terms and conditions set forth in this
53 | License apply to your use of that work.
54 |
55 | Any use of the Licensed Work in violation of this License will automatically
56 | terminate your rights under this License for the current and all other
57 | versions of the Licensed Work.
58 |
59 | This License does not grant you any right in any trademark or logo of
60 | Licensor or its affiliates (provided that you may use a trademark or logo of
61 | Licensor as expressly required by this License).
62 |
63 | TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
64 | AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
65 | EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
66 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
67 | TITLE.
68 |
69 | MariaDB hereby grants you permission to use this License’s text to license
70 | your works, and to refer to it using the trademark "Business Source License",
71 | as long as you comply with the Covenants of Licensor below.
72 |
73 | Covenants of Licensor
74 |
75 | In consideration of the right to use this License’s text and the "Business
76 | Source License" name and trademark, Licensor covenants to MariaDB, and to all
77 | other recipients of the licensed work to be provided by Licensor:
78 |
79 | 1. To specify as the Change License the GPL Version 2.0 or any later version,
80 | or a license that is compatible with GPL Version 2.0 or a later version,
81 | where "compatible" means that software provided under the Change License can
82 | be included in a program with software provided under GPL Version 2.0 or a
83 | later version. Licensor may specify additional Change Licenses without
84 | limitation.
85 |
86 | 2. To either: (a) specify an additional grant of rights to use that does not
87 | impose any additional restriction on the right granted in this License, as
88 | the Additional Use Grant; or (b) insert the text "None".
89 |
90 | 3. To specify a Change Date.
91 |
92 | 4. Not to modify this License in any other way.
93 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Pushtrix
2 |
3 | A fast paced puzzle game.
4 |
5 | Try it out at https://gottfired.github.io/pushtrix
6 |
7 | This app still has a few issues (mainly when build for iOS/Android native - see TODO.md).
8 |
9 | Won Runner Up in category multiplatform in the Flutter Puzzle Hack: https://flutterhack.devpost.com/
10 |
11 | # How to build
12 |
13 | - `flutter pub get`
14 | - make a copy of `.env.empty` and name it `.env`
15 | - `flutter pub run build_runner build --delete-conflicting-outputs`
16 | - `flutter run`
17 | - Leaderboard will not work when building the game this way, since the keys needed for hashing highscore entries are not part of this repo. This method for encrypting the leaderboard is not secure at all but at least provides some basic obfuscation to prevent casual cheaters from entering bogus scores. I deliberately didn't want to lock highscore entries behind a login to keep the old school arcade feel. So if you figure out how to enter a highscore without playing, congrats you're a 1337 h4x0r! But please don't ruin the game for others.
18 |
19 | # Known Issues
20 |
21 | - Uploading an iOS build triggers a warning email regarding push notifications that can be ignored: https://stackoverflow.com/a/55167613/677910
22 |
--------------------------------------------------------------------------------
/README_DEVELOP.md:
--------------------------------------------------------------------------------
1 | - add windows support `flutter create --platforms=windows .`
2 | - `flutter run` to start or play button in VScode debug pane
3 | - play button adds hot reload
4 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | # TODO
2 |
3 | - fix missing PWA highscore keyboard on iOS
4 | - fix iOS web sound
5 | - fix shrinking load screen (only on iOS mobile web)
6 | - deploy to stores
7 | - add more scenes
8 | - decouple ticker from background -> general refactor into better structure
9 | - fix pausing app when in start menu
10 | - fix android audio
11 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer, which statically analyzes Dart code to
2 | # check for errors, warnings, and lints.
3 | #
4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6 | # invoked from the command line by running `flutter analyze`.
7 |
8 | # The following line activates a set of recommended lints for Flutter apps,
9 | # packages, and plugins designed to encourage good coding practices.
10 | include: package:flutter_lints/flutter.yaml
11 |
12 | linter:
13 | # The lint rules applied to this project can be customized in the
14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
15 | # included above or to enable additional rules. A list of all available lints
16 | # and their documentation is published at
17 | # https://dart-lang.github.io/linter/lints/index.html.
18 | #
19 | # Instead of disabling a lint rule for the entire project in the
20 | # section below, it can also be suppressed for a single line of code
21 | # or a specific dart file by using the `// ignore: name_of_lint` and
22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
23 | # producing the lint.
24 | rules:
25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
27 |
28 | # Additional information about this file can be found at
29 | # https://dart.dev/guides/language/analysis-options
30 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/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 33
30 |
31 | compileOptions {
32 | sourceCompatibility JavaVersion.VERSION_1_8
33 | targetCompatibility JavaVersion.VERSION_1_8
34 | }
35 |
36 | kotlinOptions {
37 | jvmTarget = '1.8'
38 | }
39 |
40 | sourceSets {
41 | main.java.srcDirs += 'src/main/kotlin'
42 | }
43 |
44 | defaultConfig {
45 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
46 | applicationId "com.chengine.pushtrix"
47 | minSdkVersion 19
48 | targetSdkVersion flutter.targetSdkVersion
49 | versionCode flutterVersionCode.toInteger()
50 | versionName flutterVersionName
51 | }
52 |
53 | buildTypes {
54 | release {
55 | // TODO: Add your own signing config for the release build.
56 | // Signing with the debug keys for now, so `flutter run --release` works.
57 | signingConfig signingConfigs.debug
58 | }
59 | }
60 | }
61 |
62 | flutter {
63 | source '../..'
64 | }
65 |
66 | dependencies {
67 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
68 | }
69 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
15 |
19 |
23 |
24 |
25 |
26 |
27 |
28 |
30 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/example/flutter_puzzle/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.chengine.pushtrix
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.6.10'
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:4.1.0'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | mavenCentral()
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 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/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-6.7-all.zip
7 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
4 | def properties = new Properties()
5 |
6 | assert localPropertiesFile.exists()
7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
8 |
9 | def flutterSdkPath = properties.getProperty("flutter.sdk")
10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
12 |
--------------------------------------------------------------------------------
/assets/audio/music/bensound-extremeaction.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/assets/audio/music/bensound-extremeaction.mp3
--------------------------------------------------------------------------------
/assets/audio/music/bensound-punky.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/assets/audio/music/bensound-punky.mp3
--------------------------------------------------------------------------------
/assets/audio/music/bensound-scifi.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/assets/audio/music/bensound-scifi.mp3
--------------------------------------------------------------------------------
/assets/audio/sounds/awesome.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/assets/audio/sounds/awesome.mp3
--------------------------------------------------------------------------------
/assets/audio/sounds/beep.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/assets/audio/sounds/beep.mp3
--------------------------------------------------------------------------------
/assets/audio/sounds/beep_long.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/assets/audio/sounds/beep_long.mp3
--------------------------------------------------------------------------------
/assets/audio/sounds/click.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/assets/audio/sounds/click.wav
--------------------------------------------------------------------------------
/assets/audio/sounds/excellent.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/assets/audio/sounds/excellent.mp3
--------------------------------------------------------------------------------
/assets/audio/sounds/game_over.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/assets/audio/sounds/game_over.mp3
--------------------------------------------------------------------------------
/assets/audio/sounds/great.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/assets/audio/sounds/great.mp3
--------------------------------------------------------------------------------
/assets/audio/sounds/keep_it_up.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/assets/audio/sounds/keep_it_up.mp3
--------------------------------------------------------------------------------
/assets/audio/sounds/swish.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/assets/audio/sounds/swish.wav
--------------------------------------------------------------------------------
/assets/audio/sounds/that_was_close.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/assets/audio/sounds/that_was_close.mp3
--------------------------------------------------------------------------------
/assets/audio/sounds/too_easy.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/assets/audio/sounds/too_easy.mp3
--------------------------------------------------------------------------------
/assets/audio/sounds/well_done.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/assets/audio/sounds/well_done.mp3
--------------------------------------------------------------------------------
/assets/launcher/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/assets/launcher/icon.png
--------------------------------------------------------------------------------
/assets/launcher/icon_rounded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/assets/launcher/icon_rounded.png
--------------------------------------------------------------------------------
/build_web.sh:
--------------------------------------------------------------------------------
1 | flutter build web --base-href /pushtrix/ --web-renderer canvaskit
2 | echo "Now copy ..."
3 | cp -a build/web/. ../pushtrix/docs/
4 |
--------------------------------------------------------------------------------
/docs/private_policy.txt:
--------------------------------------------------------------------------------
1 | Pushtrix does not collect any personal data.
2 |
--------------------------------------------------------------------------------
/fonts/AzeretMono-Black.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/fonts/AzeretMono-Black.ttf
--------------------------------------------------------------------------------
/fonts/AzeretMono-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/fonts/AzeretMono-Bold.ttf
--------------------------------------------------------------------------------
/fonts/AzeretMono-ExtraBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/fonts/AzeretMono-ExtraBold.ttf
--------------------------------------------------------------------------------
/fonts/AzeretMono-SemiBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/fonts/AzeretMono-SemiBold.ttf
--------------------------------------------------------------------------------
/fonts/Rowdies-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/fonts/Rowdies-Bold.ttf
--------------------------------------------------------------------------------
/fonts/Rowdies-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/fonts/Rowdies-Light.ttf
--------------------------------------------------------------------------------
/fonts/Rowdies-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/fonts/Rowdies-Regular.ttf
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | **/dgph
2 | *.mode1v3
3 | *.mode2v3
4 | *.moved-aside
5 | *.pbxuser
6 | *.perspectivev3
7 | **/*sync/
8 | .sconsign.dblite
9 | .tags*
10 | **/.vagrant/
11 | **/DerivedData/
12 | Icon?
13 | **/Pods/
14 | **/.symlinks/
15 | profile
16 | xcuserdata
17 | **/.generated/
18 | Flutter/App.framework
19 | Flutter/Flutter.framework
20 | Flutter/Flutter.podspec
21 | Flutter/Generated.xcconfig
22 | Flutter/ephemeral/
23 | Flutter/app.flx
24 | Flutter/app.zip
25 | Flutter/flutter_assets/
26 | Flutter/flutter_export_environment.sh
27 | ServiceDefinitions.json
28 | Runner/GeneratedPluginRegistrant.*
29 |
30 | # Exceptions to above rules.
31 | !default.mode1v3
32 | !default.mode2v3
33 | !default.pbxuser
34 | !default.perspectivev3
35 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
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 | 11.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | platform :ios, '14.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | use_frameworks!
32 | use_modular_headers!
33 |
34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
35 | end
36 |
37 | post_install do |installer|
38 | installer.pods_project.targets.each do |target|
39 | flutter_additional_ios_build_settings(target)
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - app_links (0.0.1):
3 | - Flutter
4 | - audioplayers_darwin (0.0.1):
5 | - Flutter
6 | - Flutter (1.0.0)
7 | - path_provider_ios (0.0.1):
8 | - Flutter
9 | - shared_preferences_ios (0.0.1):
10 | - Flutter
11 | - url_launcher_ios (0.0.1):
12 | - Flutter
13 |
14 | DEPENDENCIES:
15 | - app_links (from `.symlinks/plugins/app_links/ios`)
16 | - audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/ios`)
17 | - Flutter (from `Flutter`)
18 | - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
19 | - shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`)
20 | - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
21 |
22 | EXTERNAL SOURCES:
23 | app_links:
24 | :path: ".symlinks/plugins/app_links/ios"
25 | audioplayers_darwin:
26 | :path: ".symlinks/plugins/audioplayers_darwin/ios"
27 | Flutter:
28 | :path: Flutter
29 | path_provider_ios:
30 | :path: ".symlinks/plugins/path_provider_ios/ios"
31 | shared_preferences_ios:
32 | :path: ".symlinks/plugins/shared_preferences_ios/ios"
33 | url_launcher_ios:
34 | :path: ".symlinks/plugins/url_launcher_ios/ios"
35 |
36 | SPEC CHECKSUMS:
37 | app_links: ab4ba54d10a13d45825336bc9707b5eadee81191
38 | audioplayers_darwin: 877d9a4d06331c5c374595e46e16453ac7eafa40
39 | Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
40 | path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
41 | shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad
42 | url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de
43 |
44 | PODFILE CHECKSUM: 775997f741c536251164e3eacf6e34abf2eb7a17
45 |
46 | COCOAPODS: 1.11.2
47 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CADisableMinimumFrameDurationOnPhone
6 |
7 | CFBundleDevelopmentRegion
8 | $(DEVELOPMENT_LANGUAGE)
9 | CFBundleDisplayName
10 | Pushtrix
11 | CFBundleExecutable
12 | $(EXECUTABLE_NAME)
13 | CFBundleIdentifier
14 | $(PRODUCT_BUNDLE_IDENTIFIER)
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundleName
18 | pushtrix
19 | CFBundlePackageType
20 | APPL
21 | CFBundleShortVersionString
22 | $(FLUTTER_BUILD_NAME)
23 | CFBundleSignature
24 | ????
25 | CFBundleVersion
26 | $(FLUTTER_BUILD_NUMBER)
27 | LSRequiresIPhoneOS
28 |
29 | UILaunchStoryboardName
30 | LaunchScreen
31 | UIMainStoryboardFile
32 | Main
33 | UIStatusBarHidden
34 |
35 | UISupportedInterfaceOrientations
36 |
37 | UIInterfaceOrientationPortrait
38 |
39 | UISupportedInterfaceOrientations~ipad
40 |
41 | UIInterfaceOrientationPortrait
42 | UIInterfaceOrientationPortraitUpsideDown
43 | UIInterfaceOrientationLandscapeLeft
44 | UIInterfaceOrientationLandscapeRight
45 |
46 | UIViewControllerBasedStatusBarAppearance
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/lib/app_lifecycle.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class AppLifecycle extends WidgetsBindingObserver {
4 | AppLifecycle({this.onResume, this.onPause});
5 |
6 | final void Function()? onResume;
7 | final void Function()? onPause;
8 |
9 | @override
10 | Future didChangeAppLifecycleState(AppLifecycleState state) async {
11 | switch (state) {
12 | case AppLifecycleState.inactive:
13 | case AppLifecycleState.paused:
14 | case AppLifecycleState.detached:
15 | onPause?.call();
16 | break;
17 | case AppLifecycleState.resumed:
18 | onResume?.call();
19 | break;
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lib/audio.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:io';
3 |
4 | import 'package:audioplayers/audioplayers.dart';
5 | import 'package:flame_audio/flame_audio.dart';
6 | import 'package:flutter/foundation.dart';
7 | import 'package:pushtrix/config.dart';
8 |
9 | const praises = [
10 | "sounds/well_done.mp3",
11 | "sounds/great.mp3",
12 | "sounds/keep_it_up.mp3",
13 | "sounds/excellent.mp3",
14 | "sounds/awesome.mp3",
15 | ];
16 |
17 | class Audio {
18 | static final Audio instance = Audio();
19 |
20 | bool settingEnabled = true;
21 |
22 | List _praiseIndexes = [];
23 | int _currentPraise = 0;
24 |
25 | AudioPlayer? _player;
26 | Timer? _timer;
27 |
28 | bool isIosWebDisabled = false;
29 |
30 | void init() async {
31 | isIosWebDisabled = kIsWeb && defaultTargetPlatform == TargetPlatform.iOS && !enableIosWebAudio;
32 |
33 | if (!isIosWebDisabled) {
34 | await FlameAudio.audioCache.loadAll([
35 | "sounds/swish.wav",
36 | "sounds/click.wav",
37 | ...praises,
38 | "sounds/game_over.mp3",
39 | "sounds/too_easy.mp3",
40 | "sounds/that_was_close.mp3",
41 | "sounds/beep.mp3",
42 | "sounds/beep_long.mp3",
43 | ]);
44 |
45 | FlameAudio.bgm.initialize();
46 | await FlameAudio.audioCache.loadAll(["music/bensound-scifi.mp3", "music/bensound-punky.mp3"]);
47 | }
48 |
49 | _praiseIndexes = List.generate(praises.length, (i) => i);
50 | _praiseIndexes.shuffle();
51 | }
52 |
53 | void dispose() {
54 | if (!isIosWebDisabled) {
55 | FlameAudio.bgm.dispose();
56 | }
57 | }
58 |
59 | bool get _enabled => settingEnabled && !isIosWebDisabled;
60 |
61 | void click() {
62 | if (!_enabled) return;
63 |
64 | FlameAudio.play("sounds/click.wav");
65 | }
66 |
67 | void swish() async {
68 | if (!_enabled) return;
69 |
70 | await FlameAudio.play("sounds/swish.wav", volume: 0.5);
71 | }
72 |
73 | void praise() async {
74 | if (!_enabled) return;
75 |
76 | await FlameAudio.play(praises[_praiseIndexes[_currentPraise++]]);
77 | if (_currentPraise >= praises.length) {
78 | _currentPraise = 0;
79 | _praiseIndexes.shuffle();
80 | }
81 | }
82 |
83 | void thatWasClose() async {
84 | if (!_enabled) return;
85 |
86 | await FlameAudio.play("sounds/that_was_close.mp3");
87 | }
88 |
89 | void gameOver() async {
90 | if (!_enabled) return;
91 |
92 | await FlameAudio.play("sounds/game_over.mp3");
93 | }
94 |
95 | void tooEasy() async {
96 | if (!_enabled) return;
97 |
98 | await FlameAudio.play("sounds/too_easy.mp3");
99 | }
100 |
101 | void beep() async {
102 | if (!_enabled) return;
103 | await FlameAudio.play("sounds/beep.mp3");
104 | }
105 |
106 | void beepLong() async {
107 | if (!_enabled) return;
108 |
109 | await FlameAudio.play("sounds/beep_long.mp3");
110 | }
111 |
112 | void playMusic(String title, {double volume = 1}) async {
113 | await FlameAudio.bgm.stop();
114 | Timer(const Duration(milliseconds: 500), () async {
115 | await FlameAudio.bgm.play(title, volume: volume);
116 | });
117 | }
118 |
119 | void menuMusic() {
120 | if (!_enabled) return;
121 |
122 | if (_timer != null) {
123 | _timer!.cancel();
124 | _timer = null;
125 | }
126 |
127 | playMusic("music/bensound-scifi.mp3");
128 | }
129 |
130 | void gameMusic() {
131 | if (!_enabled) return;
132 |
133 | playMusic("music/bensound-punky.mp3", volume: 0.4);
134 |
135 | // TODO: Fix timer starting fast music when app in background
136 | // _timer = Timer(const Duration(seconds: 125), () {
137 | // gameMusicFast();
138 | // _timer = null;
139 | // });
140 | }
141 |
142 | void gameMusicFast() async {
143 | if (!_enabled) return;
144 |
145 | playMusic("music/bensound-extremeaction.mp3");
146 | }
147 |
148 | void enable(bool enabled) async {
149 | settingEnabled = enabled;
150 | if (!enabled) {
151 | await FlameAudio.bgm.stop();
152 | await _player?.stop();
153 | _player = null;
154 | _timer?.cancel();
155 | } else {
156 | menuMusic();
157 | }
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/lib/audio_dialog.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class AudioDialog extends StatelessWidget {
4 | const AudioDialog({
5 | Key? key,
6 | }) : super(key: key);
7 |
8 | @override
9 | Widget build(BuildContext context) {
10 | return Dialog(
11 | backgroundColor: Colors.white,
12 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
13 | child: Container(
14 | padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 24),
15 | child: ConstrainedBox(
16 | constraints: const BoxConstraints(minWidth: 320), // TODO: Why is minWidth not working?
17 | child: Column(
18 | mainAxisSize: MainAxisSize.min,
19 | children: [
20 | RichText(
21 | text: TextSpan(
22 | text: "La ",
23 | style: const TextStyle(fontSize: 32, fontFamily: "Rowdies", fontWeight: FontWeight.w300, color: Colors.red),
24 | children: [
25 | TextSpan(text: 'La ', style: TextStyle(color: Colors.yellow.shade600)),
26 | const TextSpan(text: 'La ', style: TextStyle(color: Colors.blue)),
27 | const TextSpan(text: 'or ', style: TextStyle(color: Colors.black)),
28 | TextSpan(text: 'Hush?', style: TextStyle(color: Colors.grey.shade400)),
29 | ],
30 | ),
31 | ),
32 | const SizedBox(height: 16),
33 | RichText(
34 | text: TextSpan(
35 | text: "This game was designed with audio in mind.\nYou decide: ",
36 | style: const TextStyle(fontSize: 20, fontFamily: "Rowdies", fontWeight: FontWeight.w300, color: Colors.black),
37 | children: [
38 | TextSpan(text: 'fun ', style: TextStyle(color: Colors.red.shade600)),
39 | TextSpan(text: 'and ', style: TextStyle(color: Colors.yellow.shade800)),
40 | TextSpan(text: 'funky ', style: TextStyle(color: Colors.blue.shade700)),
41 | const TextSpan(text: 'or ', style: TextStyle(color: Colors.black)),
42 | TextSpan(text: 'quiet and boring?', style: TextStyle(color: Colors.grey.shade500)),
43 | ],
44 | ),
45 | ),
46 | const SizedBox(height: 48),
47 | ElevatedButton(
48 | onPressed: () {
49 | Navigator.pop(context, true);
50 | },
51 | child: const Text("Thank you for the music!", style: TextStyle(fontSize: 20, fontWeight: FontWeight.w300)),
52 | style: ElevatedButton.styleFrom(
53 | padding: const EdgeInsets.all(20),
54 | shape: const RoundedRectangleBorder(
55 | borderRadius: BorderRadius.all(Radius.circular(10)),
56 | ),
57 | ),
58 | ),
59 | const SizedBox(height: 8),
60 | TextButton(
61 | onPressed: () {
62 | Navigator.pop(context, false);
63 | },
64 | child: Text("Shh, everyone's asleep.", style: TextStyle(fontSize: 18, color: Colors.grey.shade500)),
65 | style: TextButton.styleFrom(
66 | padding: const EdgeInsets.all(20),
67 | ),
68 | ),
69 | ],
70 | ),
71 | ),
72 | ),
73 | );
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/lib/background.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/scheduler.dart';
3 | import 'package:pushtrix/scenes/cubes.dart';
4 | import 'package:pushtrix/game/game.dart';
5 | import 'package:pushtrix/scenes/particle.dart';
6 | import 'package:pushtrix/scenes/rays.dart';
7 | import 'package:pushtrix/scenes/scene.dart';
8 | import 'package:pushtrix/scenes/start_screen.dart';
9 |
10 | import 'config.dart';
11 | import 'game/game_time.dart';
12 |
13 | class BackgroundPainter extends CustomPainter {
14 | final double value;
15 | Scene? scene;
16 | Scene? nextScene;
17 | BackgroundState state;
18 |
19 | BackgroundPainter({required this.value, required this.state, this.scene, this.nextScene}) {
20 | if (nextScene != null) debugPrint('BackgroundPainter $nextScene');
21 | }
22 |
23 | /*
24 | void dummyPaint(Canvas canvas, Size size) {
25 | var paint = Paint()
26 | ..color = Colors.red
27 | ..strokeWidth = 50
28 | ..strokeCap = StrokeCap.round;
29 |
30 | final center = Offset(size.width / 2, size.height / 2);
31 | final length = min(size.width, size.height);
32 |
33 | // Paint line moving up/down
34 | final range = size.height / 3;
35 | final y = size.height / 2 + sin(value) * range;
36 | canvas.drawLine(Offset(0, y), Offset(size.width, y), paint);
37 |
38 | // Paint center pulsing circle
39 | paint = Paint()
40 | ..color = Colors.blue.shade50
41 | ..strokeWidth = 5
42 | ..strokeCap = StrokeCap.round;
43 |
44 | final rMin = length * 0.4;
45 | final rMax = length * 0.45;
46 | canvas.drawCircle(Offset(center.dx, center.dy), rMin + (rMax - rMin) * sin(4 * value), paint);
47 |
48 | // Paint rotating circles
49 | paint = Paint()..color = Colors.blue.shade100;
50 |
51 | final r2 = length / 2;
52 | canvas.drawCircle(Offset(center.dx + r2 * cos(2 * value), center.dy + r2 * sin(2 * value)), 50, paint);
53 |
54 | paint = Paint()..color = Colors.blue.shade200;
55 |
56 | final r3 = length / 3;
57 | canvas.drawCircle(Offset(center.dx + r3 * cos(2 * value), center.dy + r3 * sin(2 * value)), 20, paint);
58 | }
59 |
60 | void paintPulsingCenter(Canvas canvas, Size size) {
61 | var paint = Paint()
62 | ..color = Colors.red
63 | ..strokeWidth = 50
64 | ..strokeCap = StrokeCap.round;
65 |
66 | final center = Offset(size.width / 2, size.height / 2);
67 | final length = min(size.width, size.height);
68 |
69 | paint = Paint()
70 | ..color = Colors.blue.shade50
71 | ..strokeWidth = 5
72 | ..strokeCap = StrokeCap.round;
73 |
74 | final rMin = length * 0.3;
75 | final rMax = length * 0.35;
76 | canvas.drawCircle(Offset(center.dx, center.dy), rMin + (rMax - rMin) * sin(4 * value), paint);
77 | }
78 | */
79 |
80 | @override
81 | void paint(Canvas canvas, Size size) {
82 | if (scene == null) {
83 | return;
84 | }
85 |
86 | if (nextScene != null) {
87 | nextScene!.render(canvas, size);
88 | }
89 |
90 | scene!.render(canvas, size);
91 | }
92 |
93 | @override
94 | bool shouldRepaint(CustomPainter oldDelegate) => true;
95 | }
96 |
97 | class Background extends StatefulWidget {
98 | const Background({Key? key}) : super(key: key);
99 |
100 | @override
101 | State createState() => BackgroundState();
102 | }
103 |
104 | class BackgroundState extends State with TickerProviderStateMixin {
105 | late final AnimationController _controller;
106 |
107 | late final Ticker ticker;
108 | int frame = 0;
109 |
110 | static late BackgroundState instance;
111 |
112 | final _scenes = [
113 | Rays(),
114 | ParticleSystem(),
115 | Cubes(),
116 | ];
117 |
118 | final startScreen = StartScreen();
119 |
120 | int _currentSceneIndex = 0;
121 |
122 | BackgroundState() {
123 | BackgroundState.instance = this;
124 |
125 | if (useAnimationController) {
126 | _controller = AnimationController(
127 | duration: const Duration(seconds: 3600),
128 | upperBound: 3600,
129 | vsync: this,
130 | )..repeat();
131 | }
132 |
133 | double lastDuration = 0;
134 | ticker = Ticker((duration) {
135 | final current = duration.inMilliseconds / 1000.0;
136 | frame++;
137 |
138 | if (frame & 1 == 0) {
139 | final dt = current - lastDuration;
140 | GameTime.instance.tick(dt);
141 | lastDuration = current;
142 |
143 | _currentScene.tick();
144 | if (_currentScene.state == SceneState.fadeOut || _currentScene.state == SceneState.done) {
145 | final nextScene = _nextScene;
146 | if (nextScene != null) {
147 | nextScene.tick();
148 | }
149 |
150 | if (_currentScene.state == SceneState.done) {
151 | _currentScene.reset();
152 | _currentSceneIndex = (_currentSceneIndex + 1) % _scenes.length;
153 | }
154 | }
155 |
156 | setState(() {
157 | // debugPrint("setState");
158 | });
159 | }
160 | });
161 |
162 | ticker.start();
163 | }
164 |
165 | void pause() {
166 | ticker.stop();
167 | }
168 |
169 | void resume() {
170 | ticker.start();
171 | }
172 |
173 | Scene get _currentScene {
174 | if (Game.instance.state == GameState.startScreen) {
175 | return startScreen;
176 | } else {
177 | return _scenes[_currentSceneIndex];
178 | }
179 | }
180 |
181 | Scene? get _nextScene {
182 | return _scenes[(_currentSceneIndex + 1) % _scenes.length];
183 | }
184 |
185 | @override
186 | void dispose() {
187 | super.dispose();
188 | ticker.dispose();
189 | if (useAnimationController) {
190 | _controller.dispose();
191 | }
192 | }
193 |
194 | @override
195 | Widget build(BuildContext context) {
196 | final size = MediaQuery.of(context).size;
197 |
198 | final scene = _currentScene;
199 | final nextScene = _nextScene;
200 | final crossFade = _currentScene.state == SceneState.fadeOut;
201 |
202 | // if (crossFade) {
203 | // debugPrint("$crossFade $nextScene");
204 | // }
205 |
206 | if (useAnimationController) {
207 | return AnimatedBuilder(
208 | animation: _controller,
209 | builder: (context, _) {
210 | return CustomPaint(
211 | painter: BackgroundPainter(
212 | scene: scene,
213 | nextScene: crossFade ? nextScene : null,
214 | value: _controller.value,
215 | state: this,
216 | ),
217 | size: size,
218 | );
219 | });
220 | } else {
221 | return CustomPaint(
222 | size: size,
223 | painter: BackgroundPainter(
224 | value: GameTime.instance.current,
225 | state: this,
226 | scene: scene,
227 | nextScene: crossFade ? nextScene : null,
228 | ),
229 | );
230 | }
231 | }
232 |
233 | reset() {
234 | _currentSceneIndex = 0;
235 | for (final scene in _scenes) {
236 | scene.reset();
237 | scene.gameOver();
238 | }
239 | }
240 |
241 | // continue -> rays restart
242 | }
243 |
--------------------------------------------------------------------------------
/lib/build_context_extension.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | extension BuildContextExtension on BuildContext {
4 | bool isMobile() {
5 | return MediaQuery.of(this).size.width < 600;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/lib/config.dart:
--------------------------------------------------------------------------------
1 | const debugEnabled = false;
2 | const debugBanner = true;
3 |
4 | const screenshotIcon = false;
5 | const sizeFactor = screenshotIcon ? 4.0 : 1.0;
6 |
7 | const useAnimationController = false;
8 |
9 | const alwaysSmallPuzzles = false;
10 |
11 | const tileSize = sizeFactor * 60.0;
12 | const tileBorderSize = sizeFactor * 1.0;
13 | const puzzleBorderSize = sizeFactor * 4.0;
14 | const slideTimeMs = 100;
15 | const dropInAnimMs = 300;
16 | const resetMs = 50;
17 | const levelDurationSeconds = 10.0;
18 |
19 | const leaderboardSize = 20;
20 | const leaderboardDeleteInvalid = false;
21 |
22 | const infiniteTime = false;
23 | const enableIosWebAudio = false;
24 |
--------------------------------------------------------------------------------
/lib/env.dart:
--------------------------------------------------------------------------------
1 | import 'package:envied/envied.dart';
2 |
3 | part 'env.g.dart';
4 |
5 | @Envied(path: '.env')
6 | abstract class Env {
7 | @EnviedField(obfuscate: true)
8 | static final HASH = _Env.HASH;
9 |
10 | @EnviedField(obfuscate: true)
11 | static final SALT = _Env.SALT;
12 |
13 | @EnviedField()
14 | static const SUPABASE_URL = _Env.SUPABASE_URL;
15 |
16 | @EnviedField()
17 | static const SUPABASE_ANON_KEY = _Env.SUPABASE_ANON_KEY;
18 | }
19 |
--------------------------------------------------------------------------------
/lib/game/countdown.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter/material.dart';
4 |
5 | import 'game.dart';
6 |
7 | class Countdown extends StatefulWidget {
8 | final Game game;
9 |
10 | const Countdown(this.game, {Key? key}) : super(key: key);
11 |
12 | @override
13 | CountdownState createState() => CountdownState();
14 | }
15 |
16 | class CountdownState extends State {
17 | double _timerValue = 0;
18 |
19 | double _levelTime = 0;
20 | Timer? _timer;
21 |
22 | @override
23 | void initState() {
24 | super.initState();
25 | widget.game.setCountdownState(this);
26 | }
27 |
28 | void start(double levelTime) {
29 | stopTimer();
30 | startTimer(levelTime);
31 | }
32 |
33 | void startTimer([double? remainingTime]) {
34 | _timer = Timer.periodic(
35 | const Duration(milliseconds: 10),
36 | (Timer timer) {
37 | // debugPrint('timer tick ${timer.tick}');
38 | setState(() {
39 | if (_timerValue <= 0) {
40 | timer.cancel();
41 | widget.game.onTimerFinished();
42 | } else {
43 | _timerValue = _levelTime - timer.tick * 0.01;
44 | if (_timerValue < 0) {
45 | _timerValue = 0;
46 | }
47 |
48 | widget.game.tick(_timerValue);
49 | }
50 | });
51 | // onTimerTick?.call(_timerValue);
52 | },
53 | );
54 |
55 | setState(() {
56 | double remaining = remainingTime ?? _timerValue;
57 | _timerValue = remaining;
58 | _levelTime = remaining;
59 | });
60 | }
61 |
62 | void stopTimer() {
63 | _timer?.cancel();
64 | }
65 |
66 | void solved() {
67 | stopTimer();
68 | }
69 |
70 | @override
71 | Widget build(BuildContext context) {
72 | final screenSize = MediaQuery.of(context).size;
73 | const duration = Duration(milliseconds: 200);
74 |
75 | return AnimatedPositioned(
76 | top: screenSize.height / 2 -
77 | widget.game.getPuzzleScreenSize() / 2 -
78 | 70 -
79 | ((widget.game.showCountdown() || widget.game.levelTime == 0) ? 0 : 40),
80 | duration: duration,
81 | child: AnimatedOpacity(
82 | opacity: widget.game.showCountdown() ? 1 : 0,
83 | duration: duration,
84 | child: Container(
85 | width: 120,
86 | decoration: BoxDecoration(
87 | borderRadius: BorderRadius.circular(10),
88 | color: Colors.white,
89 | boxShadow: [
90 | BoxShadow(
91 | color: Colors.blue.shade400.withOpacity(0.5),
92 | spreadRadius: 3,
93 | blurRadius: 6,
94 | offset: const Offset(0, 3), // changes position of shadow
95 | ),
96 | ],
97 | ),
98 | child: Padding(
99 | padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
100 | child: Column(
101 | children: [
102 | Row(
103 | mainAxisAlignment: MainAxisAlignment.center,
104 | children: [
105 | Text(
106 | _timerValue.toInt().toString(),
107 | style: TextStyle(
108 | fontSize: 30,
109 | fontWeight: FontWeight.bold,
110 | fontFamily: "AzeretMono",
111 | color: Colors.blue.shade600,
112 | ),
113 | ),
114 | const SizedBox(width: 10),
115 | Text(
116 | (_timerValue - _timerValue.toInt()).toStringAsFixed(2).substring(2),
117 | style: TextStyle(
118 | fontSize: 20,
119 | fontWeight: FontWeight.bold,
120 | fontFamily: "AzeretMono",
121 | color: Colors.blue.shade600,
122 | ),
123 | ),
124 | ],
125 | ),
126 | if (Game.instance.levelTime > 0)
127 | LinearProgressIndicator(
128 | value: _timerValue / Game.instance.levelTime,
129 | backgroundColor: Colors.blue.shade50,
130 | ),
131 | ],
132 | ),
133 | ),
134 | ),
135 | ),
136 | );
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/lib/game/game_time.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 |
3 | class GameTime {
4 | static late GameTime instance;
5 |
6 | static void init() {
7 | GameTime.instance = GameTime();
8 | }
9 |
10 | double current = 0;
11 | double dt = 0;
12 |
13 | void tick(double delta) {
14 | if (delta < 0) {
15 | return;
16 | }
17 |
18 | current += delta;
19 | dt = delta;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/lib/game/grid.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:pushtrix/game/puzzle.dart';
3 |
4 | import '../config.dart';
5 | import 'tile.dart';
6 |
7 | class Grid extends StatelessWidget {
8 | final Puzzle _puzzle;
9 | final Function(int number)? onTap;
10 |
11 | final bool? withShadow;
12 |
13 | const Grid(this._puzzle, {Key? key, this.onTap, this.withShadow}) : super(key: key);
14 |
15 | @override
16 | Widget build(BuildContext context) {
17 | // Fill list of tiles
18 | List tiles = [];
19 | for (int i = 0; i < _puzzle.tiles.length; i++) {
20 | final number = _puzzle.tiles[i];
21 | final row = i ~/ _puzzle.size;
22 | final col = i % _puzzle.size;
23 | // 0 is the empty space
24 | if (number != 0) {
25 | tiles.add(
26 | // Animated so tile pushing is smooth
27 | AnimatedPositioned(
28 | key: ValueKey(number),
29 | left: col * tileSize,
30 | top: row * tileSize,
31 | duration: const Duration(milliseconds: slideTimeMs),
32 | child: Tile(
33 | number,
34 | onTap,
35 | _puzzle.canMoveHorizontal(number),
36 | _puzzle.canMoveVertical(number),
37 | ),
38 | ),
39 | );
40 | }
41 | }
42 |
43 | // Render the container for the tiles
44 | return Container(
45 | decoration: BoxDecoration(
46 | border: Border.all(
47 | color: Colors.blue,
48 | width: puzzleBorderSize,
49 | ),
50 | borderRadius: BorderRadius.circular(tileSize / 5),
51 | boxShadow: [
52 | if (withShadow != false)
53 | BoxShadow(
54 | color: Colors.blue.shade400.withOpacity(0.5),
55 | spreadRadius: 2,
56 | blurRadius: 4,
57 | offset: const Offset(0, 3), // changes position of shadow
58 | ),
59 | ],
60 | ),
61 | child: Stack(
62 | // Render tiles
63 | children: [
64 | Container(color: Colors.blue, width: tileSize * _puzzle.size, height: tileSize * _puzzle.size),
65 | ...tiles,
66 | ],
67 | ),
68 | );
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/lib/game/puzzle.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 | import 'dart:ui';
3 |
4 | import 'package:flutter/material.dart';
5 | import 'package:pushtrix/config.dart';
6 |
7 | class Puzzle {
8 | final List tiles = [];
9 | List lastTiles = [];
10 | final List _empty;
11 | final int size;
12 | double _animTime = 0;
13 |
14 | static double getScreenSize(int puzzleHeight) {
15 | return puzzleHeight * tileSize + 2 * puzzleBorderSize;
16 | }
17 |
18 | double get screenSize {
19 | return size * tileSize + 2 * puzzleBorderSize;
20 | }
21 |
22 | Puzzle(this.size, int shuffleCount) : _empty = [size - 1, size - 1] {
23 | reset();
24 |
25 | if (shuffleCount > 0) {
26 | while (isSolved()) {
27 | reset();
28 | shuffle(shuffleCount);
29 | }
30 | }
31 | }
32 |
33 | void reset() {
34 | tiles.clear();
35 | for (int i = 0; i < size * size - 1; i++) {
36 | tiles.add(i + 1);
37 | }
38 |
39 | // Empty is marked with 0
40 | tiles.add(0);
41 | }
42 |
43 | void shuffle(int shuffleCount) {
44 | for (var i = 0; i < shuffleCount; ++i) {
45 | doRandomMove();
46 | }
47 | }
48 |
49 | void move(int number) {
50 | if (number <= 0 || number > size * size - 1) {
51 | return;
52 | }
53 |
54 | // find coords of number
55 | final coords = _getCoords(number);
56 | final x = coords[0];
57 | final y = coords[1];
58 |
59 | if (x == _empty[0]) {
60 | _moveColumn(y);
61 | _debugOutput();
62 | } else if (y == _empty[1]) {
63 | _moveRow(x);
64 | _debugOutput();
65 | }
66 | }
67 |
68 | bool isSolved() {
69 | for (int i = 0; i < size * size - 1; i++) {
70 | if (tiles[i] != i + 1) {
71 | return false;
72 | }
73 | }
74 |
75 | return true;
76 | }
77 |
78 | void _startTileMoveAnim() {
79 | lastTiles = tiles.toList(growable: false);
80 | _animTime = 0;
81 | }
82 |
83 | void tickTileMoveAnim(double dt) {
84 | _animTime += dt * 5;
85 | }
86 |
87 | Offset getTileOffset(int number) {
88 | final coords = _getCoords(number);
89 | if (_animTime >= 1) {
90 | return Offset(coords[0] * tileSize + puzzleBorderSize, coords[1] * tileSize + puzzleBorderSize);
91 | }
92 |
93 | final lastCoords = _getCoords(number, true);
94 | final lastX = lastCoords[0];
95 | final lastY = lastCoords[1];
96 |
97 | if (coords[0] == lastX && coords[1] == lastY) {
98 | return Offset(coords[0] * tileSize + puzzleBorderSize, coords[1] * tileSize + puzzleBorderSize);
99 | }
100 |
101 | // Animate from last position to current position using animTime
102 | final x = lerpDouble(lastX, coords[0], _animTime) ?? coords[0];
103 | final y = lerpDouble(lastY, coords[1], _animTime) ?? coords[1];
104 |
105 | return Offset(x * tileSize + puzzleBorderSize, y * tileSize + puzzleBorderSize);
106 | }
107 |
108 | void _moveRow(int newX) {
109 | _startTileMoveAnim();
110 | final y = _empty[1];
111 | if (newX < _empty[0]) {
112 | // Move tiles right
113 | for (var i = _empty[0]; i > newX; i--) {
114 | tiles[y * size + i] = tiles[y * size + i - 1];
115 | }
116 | } else {
117 | // Move tiles left
118 | for (var i = _empty[0]; i < newX; i++) {
119 | tiles[y * size + i] = tiles[y * size + i + 1];
120 | }
121 | }
122 |
123 | // Mark the new empty space
124 | _empty[0] = newX;
125 | tiles[y * size + newX] = 0;
126 | }
127 |
128 | void _moveColumn(int newY) {
129 | _startTileMoveAnim();
130 | final x = _empty[0];
131 | if (newY < _empty[1]) {
132 | // Move tiles down
133 | for (var i = _empty[1]; i > newY; i--) {
134 | tiles[i * size + x] = tiles[(i - 1) * size + x];
135 | }
136 | } else {
137 | // Move tiles up
138 | for (var i = _empty[1]; i < newY; i++) {
139 | tiles[i * size + x] = tiles[(i + 1) * size + x];
140 | }
141 | }
142 |
143 | // Mark the new empty space
144 | _empty[1] = newY;
145 | tiles[newY * size + x] = 0;
146 | }
147 |
148 | List _getCoords(int number, [bool last = false]) {
149 | List t = last && lastTiles.isNotEmpty ? lastTiles : tiles;
150 | for (var y = 0; y < size; ++y) {
151 | for (var x = 0; x < size; ++x) {
152 | if (t[y * size + x] == number) {
153 | return [x, y];
154 | }
155 | }
156 | }
157 | return [];
158 | }
159 |
160 | bool canMoveHorizontal(int number) {
161 | if (number <= 0 || number > size * size - 1) {
162 | return false;
163 | }
164 |
165 | final coords = _getCoords(number);
166 | return coords[1] == _empty[1];
167 | }
168 |
169 | bool canMoveVertical(int number) {
170 | if (number <= 0 || number > size * size - 1) {
171 | return false;
172 | }
173 |
174 | final coords = _getCoords(number);
175 | return coords[0] == _empty[0];
176 | }
177 |
178 | // Debug print to console
179 | void _debugOutput() {
180 | if (!debugEnabled) {
181 | return;
182 | }
183 |
184 | var line = "";
185 | for (var i = 0; i < size; i++) {
186 | for (var j = 0; j < size; j++) {
187 | if (tiles[i * size + j] != 0) {
188 | line += tiles[i * size + j].toString().padLeft(2) + " ";
189 | } else {
190 | line += " ";
191 | }
192 | }
193 | debugPrint(line);
194 | line = "";
195 | }
196 |
197 | debugPrint(line);
198 | }
199 |
200 | void doRandomMove() {
201 | final xOrY = Random().nextInt(2);
202 | if (xOrY == 0) {
203 | // Fill array with empty x values
204 | final empty = [for (var j = 0; j < size - 1; j++) j < _empty[0] ? j : j + 1];
205 | final newEmpty = empty[Random().nextInt(size - 1)];
206 | // Move row starting from newEmpty value
207 | _moveRow(newEmpty);
208 | } else {
209 | // Fill array with empty y values
210 | final empty = [for (var j = 0; j < size - 1; j++) j < _empty[1] ? j : j + 1];
211 | final newEmpty = empty[Random().nextInt(size - 1)];
212 | // Move column starting from newEmpty value
213 | _moveColumn(newEmpty);
214 | }
215 | }
216 | }
217 |
--------------------------------------------------------------------------------
/lib/game/save_game.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:shared_preferences/shared_preferences.dart';
3 |
4 | class SaveGame {
5 | late final SharedPreferences _prefs;
6 |
7 | int maxLevel = 0;
8 | bool finishedOnce = false;
9 | bool soundEnabled = true;
10 |
11 | static late final SaveGame instance;
12 |
13 | SaveGame() {
14 | SaveGame.instance = this;
15 | }
16 |
17 | Future init() async {
18 | _prefs = await SharedPreferences.getInstance();
19 | maxLevel = _prefs.getInt("maxLevel") ?? 0;
20 | finishedOnce = _prefs.getBool("finishedOnce") ?? false;
21 | soundEnabled = _prefs.getBool("soundEnabled") ?? true;
22 | debugPrint("load sound: $soundEnabled");
23 | }
24 |
25 | Future saveLevel(int level) async {
26 | if (level > maxLevel) {
27 | maxLevel = level;
28 | await _prefs.setInt("maxLevel", level);
29 | }
30 | }
31 |
32 | Future enableSound(bool enabled) async {
33 | soundEnabled = enabled;
34 | debugPrint("save sound: $enabled");
35 | await _prefs.setBool("soundEnabled", enabled);
36 | }
37 |
38 | Future gameOver(int level) async {
39 | finishedOnce = true;
40 | await _prefs.setBool("finishedOnce", true);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/lib/game/tile.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import '../audio.dart';
4 | import '../config.dart';
5 |
6 | class Tile extends StatelessWidget {
7 | final int number;
8 | final Function(int number)? onTap;
9 | final bool canMoveHorizontal;
10 | final bool canMoveVertical;
11 | final bool? red;
12 |
13 | const Tile(this.number, this.onTap, this.canMoveHorizontal, this.canMoveVertical, {Key? key, this.red}) : super(key: key);
14 |
15 | @override
16 | Widget build(BuildContext context) {
17 | return GestureDetector(
18 | onPanDown: (_) {
19 | onTap?.call(number);
20 | Audio.instance.swish();
21 | },
22 | // onVerticalDragStart: (details) {
23 | // if (canMoveVertical) {
24 | // onTap?.call(number);
25 | // }
26 | // },
27 | // onHorizontalDragStart: (details) {
28 | // if (canMoveHorizontal) {
29 | // onTap?.call(number);
30 | // }
31 | // },
32 | child: MouseRegion(
33 | cursor: SystemMouseCursors.click,
34 | child: Container(
35 | width: tileSize,
36 | height: tileSize,
37 | decoration: BoxDecoration(
38 | color: Colors.white,
39 | border: Border.all(
40 | color: Colors.blue,
41 | width: tileBorderSize,
42 | ),
43 | borderRadius: BorderRadius.circular(tileSize / 5),
44 | ),
45 | child: Center(
46 | child: Text(
47 | '$number',
48 | style: TextStyle(
49 | fontSize: tileSize / 2,
50 | fontWeight: FontWeight.bold,
51 | color: Colors.blue.shade800,
52 | ),
53 | ),
54 | ),
55 | ),
56 | ),
57 | );
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/lib/highscore_dialog.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/services.dart';
3 | import 'package:pushtrix/build_context_extension.dart';
4 | import 'package:pushtrix/config.dart';
5 | import 'package:pushtrix/leaderboard.dart';
6 | import 'package:pushtrix/leaderboard_dialog.dart';
7 |
8 | class UpperCaseTextFormatter extends TextInputFormatter {
9 | @override
10 | TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
11 | return TextEditingValue(
12 | text: newValue.text.toUpperCase(),
13 | selection: newValue.selection,
14 | );
15 | }
16 | }
17 |
18 | class HighScoreDialog extends StatefulWidget {
19 | final int position;
20 | final int score;
21 |
22 | HighScoreDialog(this.position, this.score, {Key? key}) : super(key: key);
23 |
24 | @override
25 | State createState() => _HighScoreDialogState();
26 | }
27 |
28 | class _HighScoreDialogState extends State {
29 | String name = "";
30 | bool sendEnabled = false;
31 | bool sending = false;
32 |
33 | void send() async {
34 | sending = true;
35 | await saveHighScore(name, widget.score);
36 | Navigator.pop(context, true);
37 | sending = false;
38 | }
39 |
40 | @override
41 | Widget build(BuildContext context) {
42 | final textStyle = getLeaderboardTextStyle(context);
43 |
44 | const range = 2;
45 | const first = 0;
46 | const last = leaderboardSize - 1;
47 | var start = widget.position - range;
48 | var end = widget.position + range;
49 |
50 | if (start < first) {
51 | final diff = -start;
52 | start = first;
53 | end += diff;
54 | } else if (end > last) {
55 | final diff = end - last;
56 | start -= diff;
57 | end = last;
58 | }
59 |
60 | List entries = [];
61 | for (int i = start; i < widget.position; i++) {
62 | final widget = LeaderboardEntryWidget(
63 | name: leaderboard[i].name,
64 | score: leaderboard[i].score,
65 | marginBottom: 8,
66 | );
67 | entries.add(widget);
68 | }
69 |
70 | final rankColor = getRankColor(getRank(widget.score));
71 | final rankStyle = textStyle.copyWith(color: rankColor);
72 | entries.add(
73 | Container(
74 | width: 320,
75 | margin: const EdgeInsets.only(bottom: 8),
76 | child: Row(
77 | mainAxisSize: MainAxisSize.max,
78 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
79 | crossAxisAlignment: CrossAxisAlignment.center,
80 | children: [
81 | Expanded(
82 | child: Padding(
83 | padding: const EdgeInsets.only(top: 8, right: 32),
84 | child: TextField(
85 | maxLength: 14,
86 | style: rankStyle,
87 | inputFormatters: [
88 | FilteringTextInputFormatter.deny(RegExp("[ ]")),
89 | UpperCaseTextFormatter(),
90 | ],
91 | autofocus: true,
92 | decoration: const InputDecoration(
93 | hintText: 'Enter Name',
94 | counterText: '',
95 | isCollapsed: true,
96 | contentPadding: EdgeInsets.only(bottom: 8),
97 | ),
98 | onChanged: (value) {
99 | name = value;
100 | setState(() {
101 | name.isNotEmpty ? sendEnabled = true : sendEnabled = false;
102 | });
103 | },
104 | onEditingComplete: name.isNotEmpty ? send : null,
105 | ),
106 | ),
107 | ),
108 | Text(widget.score > 0 ? "LVL ${getScoreString(widget.score)}" : "-", style: rankStyle),
109 | ],
110 | ),
111 | ),
112 | );
113 |
114 | for (int i = widget.position; i < end; i++) {
115 | final widget = LeaderboardEntryWidget(
116 | name: leaderboard[i].name,
117 | score: leaderboard[i].score,
118 | marginBottom: 8,
119 | );
120 | entries.add(widget);
121 | }
122 |
123 | return Center(
124 | child: SingleChildScrollView(
125 | child: Dialog(
126 | backgroundColor: Colors.white,
127 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
128 | child: Container(
129 | padding: EdgeInsets.symmetric(horizontal: context.isMobile() ? 16 : 32, vertical: 24),
130 | child: ConstrainedBox(
131 | constraints: const BoxConstraints(minWidth: 320),
132 | child: Column(
133 | mainAxisSize: MainAxisSize.min,
134 | children: [
135 | Text(
136 | "New High Score!",
137 | style: textStyle.copyWith(fontSize: 24),
138 | ),
139 | const SizedBox(height: 16),
140 | ...entries,
141 | const SizedBox(height: 32),
142 | buildSendButton(context),
143 | ],
144 | ),
145 | ),
146 | ),
147 | ),
148 | ),
149 | );
150 | }
151 |
152 | ElevatedButton buildSendButton(BuildContext context) {
153 | return ElevatedButton(
154 | onPressed: (sendEnabled && !sending) ? send : null,
155 | child: const Text("Send", style: TextStyle(fontSize: 20, fontWeight: FontWeight.w300)),
156 | style: ElevatedButton.styleFrom(
157 | padding: const EdgeInsets.all(20),
158 | shape: const RoundedRectangleBorder(
159 | borderRadius: BorderRadius.all(Radius.circular(10)),
160 | ),
161 | ),
162 | );
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/lib/leaderboard.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:crypto/crypto.dart';
4 | import 'package:eval_ex/expression.dart';
5 | import 'package:flutter/foundation.dart';
6 | import 'package:pushtrix/config.dart';
7 | import 'package:supabase_flutter/supabase_flutter.dart';
8 |
9 | import 'env.dart';
10 |
11 | final supabase = Supabase.instance.client;
12 |
13 | class LeaderboardEntry {
14 | String name = "";
15 | int score = 0;
16 | }
17 |
18 | List leaderboard = [];
19 | List topScores = [];
20 |
21 | String scoreToHash(String name, int score, double salt) {
22 | final lower = name.toLowerCase();
23 | var saltedScore = utf8.encode("$lower$score${Env.HASH}$salt");
24 | final hash = sha256.convert(saltedScore).toString();
25 |
26 | return hash;
27 | }
28 |
29 | int scoreFromHash(String name, String hash, double salt) {
30 | final lower = name.toLowerCase();
31 |
32 | // reverse hash
33 | for (int i = 0; i < 1000; ++i) {
34 | var saltedScore = utf8.encode("$lower$i${Env.HASH}$salt");
35 | final value = sha256.convert(saltedScore).toString();
36 | if (value == hash) {
37 | return i;
38 | }
39 | }
40 |
41 | return -1;
42 | }
43 |
44 | Future refreshLeaderboard([int newScore = 0]) async {
45 | leaderboard.clear();
46 | try {
47 | debugPrint("Refresh leaderboard");
48 | final data = await supabase
49 | .from("leaderboard")
50 | .select("id,name,salt,hash")
51 | .order("salt", ascending: true)
52 | .order("created_at", ascending: true)
53 | .order("hash")
54 | .limit(leaderboardSize + 10) // +10 to add some buffer for illegal score entries
55 | ;
56 |
57 | for (int i = 0; i < leaderboardSize; ++i) {
58 | final value = data[i];
59 | final name = value['name'];
60 | final salt = value["salt"];
61 | final hash = value['hash'];
62 | final score = hash != null ? scoreFromHash(name, hash, salt) : -1;
63 | final scoreValid = score >= 0;
64 | if (scoreValid) {
65 | LeaderboardEntry leaderboardEntry = LeaderboardEntry();
66 | leaderboardEntry.name = name;
67 | leaderboardEntry.score = score;
68 | leaderboard.add(leaderboardEntry);
69 | } else {
70 | // Delete invalid entry
71 | if (leaderboardDeleteInvalid) {
72 | await supabase.from("leaderboard").delete().eq("id", value["id"]);
73 | }
74 | }
75 | }
76 | } catch (error) {
77 | // TODO: Proper error handling
78 | debugPrint("Error refreshing leaderboard: $error");
79 | }
80 |
81 | // If leaderboard is not full -> fill up with "PUSHTRIX" entries
82 | if (leaderboard.length < leaderboardSize) {
83 | for (int i = leaderboard.length; i < leaderboardSize; i++) {
84 | LeaderboardEntry leaderboardEntry = LeaderboardEntry();
85 | leaderboardEntry.name = "PUSHTRIX";
86 | leaderboardEntry.score = 0;
87 | leaderboard.add(leaderboardEntry);
88 | }
89 | }
90 |
91 | List scores = [];
92 | for (LeaderboardEntry entry in leaderboard) {
93 | scores.add(entry.score);
94 | }
95 |
96 | scores.add(newScore);
97 |
98 | topScores = scores.toSet().toList();
99 | topScores.sort((a, b) => b.compareTo(a));
100 | }
101 |
102 | String getScoreString(int score) {
103 | int top = topScores[0];
104 | int numDigits = top.toString().length;
105 | return score.toString().padLeft(numDigits, "0");
106 | }
107 |
108 | // Returns index in leaderboard or -1
109 | Future isHighScore(int score) async {
110 | await refreshLeaderboard(score);
111 | if (score <= leaderboard.last.score) {
112 | return -1;
113 | }
114 |
115 | for (int i = 0; i < leaderboard.length; i++) {
116 | if (leaderboard[i].score < score) {
117 | return i;
118 | }
119 | }
120 |
121 | return -1;
122 | }
123 |
124 | int getRank(int score) {
125 | for (int i = 0; i < topScores.length; ++i) {
126 | if (topScores[i] == score) {
127 | return i;
128 | }
129 | }
130 |
131 | return -1;
132 | }
133 |
134 | Future saveHighScore(String name, int score) async {
135 | try {
136 | final salt = Expression(Env.SALT).setStringVariable("x", score.toString()).eval()?.toDouble() ?? 0.0;
137 | await supabase.from("leaderboard").insert({
138 | "name": name.toUpperCase(),
139 | "salt": salt,
140 | "hash": scoreToHash(name, score, salt),
141 | });
142 | } on PostgrestException catch (error) {
143 | // 23505 is unique key violation, that's ok since we only allow once name/score combo in the leaderboard
144 | if (error.code != "23505") {
145 | // TODO: Proper error handling
146 | print("Database error saving highscore: $error");
147 | }
148 | } catch (error) {
149 | // TODO: Proper error handling
150 | print("Error saving highscore: $error");
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/lib/leaderboard_dialog.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:pushtrix/build_context_extension.dart';
3 | import 'package:pushtrix/leaderboard.dart';
4 |
5 | Color getRankColor(int rank) {
6 | if (rank == 0) {
7 | return Colors.red.shade600;
8 | } else if (rank == 1) {
9 | return Colors.yellow.shade800;
10 | } else if (rank == 2) {
11 | return Colors.blue.shade700;
12 | }
13 |
14 | return Colors.black;
15 | }
16 |
17 | TextStyle getLeaderboardTextStyle(BuildContext context) {
18 | return TextStyle(fontSize: context.isMobile() ? 18 : 20, fontFamily: "AzeretMono");
19 | }
20 |
21 | class LeaderboardDialog extends StatelessWidget {
22 | const LeaderboardDialog({
23 | Key? key,
24 | }) : super(key: key);
25 |
26 | @override
27 | Widget build(BuildContext context) {
28 | final entries = leaderboard.map((e) {
29 | return LeaderboardEntryWidget(name: e.name, score: e.score);
30 | }).toList();
31 |
32 | final textStyle = getLeaderboardTextStyle(context);
33 |
34 | return Dialog(
35 | backgroundColor: Colors.white,
36 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
37 | child: Container(
38 | padding: EdgeInsets.symmetric(horizontal: context.isMobile() ? 0 : 16, vertical: 24),
39 | child: ConstrainedBox(
40 | constraints: const BoxConstraints(minWidth: 320),
41 | child: Column(
42 | mainAxisSize: MainAxisSize.min,
43 | children: [
44 | Text(
45 | "HIGH SCORES",
46 | style: textStyle.copyWith(fontSize: 24),
47 | ),
48 | const SizedBox(height: 16),
49 | buildContent(entries),
50 | const SizedBox(height: 32),
51 | buildCloseButton(context),
52 | ],
53 | ),
54 | ),
55 | ),
56 | );
57 | }
58 |
59 | Flexible buildContent(List entries) {
60 | return Flexible(
61 | child: Theme(
62 | data: ThemeData(scrollbarTheme: const ScrollbarThemeData(thumbVisibility: MaterialStatePropertyAll(true))),
63 | child: SingleChildScrollView(
64 | child: Padding(
65 | padding: const EdgeInsets.symmetric(horizontal: 16),
66 | child: Column(children: [
67 | ...entries,
68 | ]),
69 | ),
70 | ),
71 | ),
72 | );
73 | }
74 |
75 | ElevatedButton buildCloseButton(BuildContext context) {
76 | return ElevatedButton(
77 | onPressed: () {
78 | Navigator.pop(context, true);
79 | },
80 | child: const Text("Close", style: TextStyle(fontSize: 20, fontWeight: FontWeight.w300)),
81 | style: ElevatedButton.styleFrom(
82 | padding: const EdgeInsets.all(20),
83 | shape: const RoundedRectangleBorder(
84 | borderRadius: BorderRadius.all(Radius.circular(10)),
85 | ),
86 | ),
87 | );
88 | }
89 | }
90 |
91 | class LeaderboardEntryWidget extends StatelessWidget {
92 | const LeaderboardEntryWidget({
93 | Key? key,
94 | required this.name,
95 | required this.score,
96 | this.marginBottom = 4,
97 | }) : super(key: key);
98 |
99 | final String name;
100 | final int score;
101 | final double marginBottom;
102 |
103 | @override
104 | Widget build(BuildContext context) {
105 | final color = getRankColor(getRank(score));
106 | final textStyle = getLeaderboardTextStyle(context).copyWith(color: color);
107 |
108 | return Container(
109 | width: 320,
110 | margin: EdgeInsets.only(bottom: marginBottom),
111 | child: Row(
112 | mainAxisSize: MainAxisSize.max,
113 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
114 | children: [
115 | Flexible(child: Text(name.toUpperCase(), style: textStyle)),
116 | Text(score > 0 ? "LVL ${getScoreString(score)}" : "-", style: textStyle),
117 | ],
118 | ),
119 | );
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/lib/lerp_value.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:flutter/material.dart';
4 |
5 | class LerpValue {
6 | double value = 0;
7 | double currentTime = 0;
8 | double from = 0;
9 | double to = 0;
10 | double durationSeconds = 0;
11 | final curve = Curves.easeInOut;
12 |
13 | LerpValue(double? value) {
14 | if (value != null) {
15 | this.value = value;
16 | from = value;
17 | to = value;
18 | }
19 | }
20 |
21 | void set(double value) {
22 | this.value = value;
23 | from = value;
24 | to = value;
25 | durationSeconds = 0;
26 | }
27 |
28 | void lerpTo(double to, [double durationSeconds = 1]) {
29 | if (to == this.to && durationSeconds == this.durationSeconds) {
30 | return;
31 | }
32 |
33 | from = value;
34 | this.to = to;
35 | this.durationSeconds = durationSeconds;
36 | currentTime = 0;
37 | }
38 |
39 | void tick(double dt) {
40 | if (durationSeconds == 0) {
41 | value = to;
42 | return;
43 | }
44 |
45 | currentTime += dt;
46 | if (currentTime >= durationSeconds) {
47 | value = to;
48 | durationSeconds = 0;
49 | } else {
50 | final t = max(0.0, min(1.0, currentTime / durationSeconds));
51 | value = from + curve.transform(t) * (to - from);
52 | }
53 | }
54 |
55 | bool get isLerping => durationSeconds > 0;
56 | }
57 |
--------------------------------------------------------------------------------
/lib/music_credits.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class MusicCredits extends StatelessWidget {
4 | const MusicCredits({
5 | Key? key,
6 | required bool creditsShown,
7 | }) : _creditsShown = creditsShown,
8 | super(key: key);
9 |
10 | final bool _creditsShown;
11 |
12 | @override
13 | Widget build(BuildContext context) {
14 | return AnimatedOpacity(
15 | duration: const Duration(milliseconds: 500),
16 | opacity: _creditsShown ? 1 : 0,
17 | child: Container(
18 | decoration: BoxDecoration(
19 | color: Colors.white,
20 | borderRadius: BorderRadius.circular(8),
21 | boxShadow: [
22 | BoxShadow(
23 | color: Colors.black.withOpacity(0.3),
24 | blurRadius: 8,
25 | offset: const Offset(2, 4),
26 | ),
27 | ],
28 | ),
29 | padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
30 | child: const Text("Music by Bensound.com"),
31 | ),
32 | );
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/lib/scenes/cubes.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:pushtrix/game/game_time.dart';
5 | import 'package:pushtrix/lerp_value.dart';
6 | import 'package:pushtrix/scenes/scene.dart';
7 | import 'package:vector_math/vector_math_64.dart';
8 |
9 | final cube = [
10 | // Back face
11 | Vector4(1, 1, -1, 1),
12 | Vector4(1, -1, -1, 1),
13 | Vector4(-1, -1, -1, 1),
14 | Vector4(-1, 1, -1, 1),
15 |
16 | // Front face
17 | Vector4(1, 1, 1, 1),
18 | Vector4(1, -1, 1, 1),
19 | Vector4(-1, -1, 1, 1),
20 | Vector4(-1, 1, 1, 1),
21 | ];
22 |
23 | final cubeFaces = [
24 | // back
25 | Vector4(0, 1, 2, 3),
26 | // front
27 | Vector4(7, 6, 5, 4),
28 | // sides
29 | Vector4(0, 4, 5, 1),
30 | Vector4(1, 5, 6, 2),
31 | Vector4(2, 6, 7, 3),
32 | Vector4(3, 7, 4, 0),
33 | ];
34 |
35 | const numCubes = 9;
36 | const start1 = 0;
37 | const start3 = 1;
38 | const start5 = 4;
39 | const start7 = 7;
40 | const start9 = 10;
41 | const startFade = 8;
42 | const fadeOut = 18;
43 |
44 | class Cubes extends Scene {
45 | List _lerpValues = [];
46 |
47 | double time = 0;
48 | final _backFaceFade = LerpValue(0);
49 | final _frontFaceFade = LerpValue(0);
50 |
51 | late final List _faceNormals = [];
52 |
53 | Cubes() {
54 | reset();
55 | for (final face in cubeFaces) {
56 | final v1 = cube[face[0].toInt()];
57 | final v2 = cube[face[1].toInt()];
58 | final v3 = cube[face[2].toInt()];
59 |
60 | final v1v2 = v2 - v1;
61 | final v1v3 = v3 - v1;
62 |
63 | final faceNormal = v1v2.xyz.cross(v1v3.xyz);
64 | faceNormal.normalize();
65 | debugPrint("faceNormal: $faceNormal");
66 | _faceNormals.add(Vector4(faceNormal.x, faceNormal.y, faceNormal.z, 0));
67 | }
68 | }
69 |
70 | void drawFace(Canvas canvas, Paint paint, List vertices, Vector4 face, double centerX, double centerY) {
71 | final v1 = vertices[face[0].toInt()];
72 | final v2 = vertices[face[1].toInt()];
73 | final v3 = vertices[face[2].toInt()];
74 | final v4 = vertices[face[3].toInt()];
75 |
76 | canvas.drawPath(
77 | Path()
78 | ..moveTo(v1.x * centerX / v1.w + centerX, v1.y * centerY / v1.w + centerY)
79 | ..lineTo(v2.x * centerX / v2.w + centerX, v2.y * centerY / v2.w + centerY)
80 | ..lineTo(v3.x * centerX / v3.w + centerX, v3.y * centerY / v3.w + centerY)
81 | ..lineTo(v4.x * centerX / v4.w + centerX, v4.y * centerY / v4.w + centerY)
82 | ..close(),
83 | paint,
84 | );
85 | }
86 |
87 | @override
88 | void render(Canvas canvas, Size size) {
89 | final rz = Matrix4.rotationZ(time * 1);
90 | final ry = Matrix4.rotationY(time * 0.0);
91 | final rx = Matrix4.rotationX(time * 4);
92 |
93 | final camPos = Vector3(0, 0, (sin(time * 2) + 1) * 3 + 2.0);
94 | final camUp = Vector3(cos(time), sin(time), 0);
95 | final view = makeViewMatrix(camPos, Vector3(0, 0, 0), camUp);
96 |
97 | // final rz = Matrix4.rotationZ(time * 0.0);
98 | // final ry = Matrix4.rotationY(time * 0.0);
99 | // final rx = Matrix4.rotationX(time * 0.0);
100 |
101 | // final camPos = Vector3(0, 0, 5);
102 | // final view = makeViewMatrix(Vector3(0, 0, 5), Vector3(0, 0, 0), Vector3(0, 1, 0));
103 |
104 | final projection = makePerspectiveMatrix(pi / 3, size.width / size.height, 0.1, 100);
105 | final vp = projection * view;
106 |
107 | // calculate center
108 | final cx = size.width / 2;
109 | final cy = size.height / 2;
110 |
111 | final paint = Paint()..strokeWidth = 4.0;
112 |
113 | const dist = 3.5;
114 |
115 | for (var i = 0; i < numCubes; i++) {
116 | for (var j = 0; j < numCubes; j++) {
117 | final value = _lerpValues[i * numCubes + j].value;
118 |
119 | final pos = Vector3(
120 | (i - numCubes ~/ 2) * dist,
121 | (j - numCubes ~/ 2) * dist,
122 | -8 - value * 200,
123 | );
124 | final translation = Matrix4.translation(pos);
125 |
126 | paint.style = PaintingStyle.stroke;
127 | paint.strokeWidth = 3.0;
128 |
129 | final mvp = vp * translation * rz * ry * rx;
130 | List out = [];
131 | for (final v in cube) {
132 | out.add(mvp.transformed(v));
133 | }
134 |
135 | final mv = view * rz * ry * rx;
136 | List outNormals = [];
137 | for (var i = 0; i < _faceNormals.length; i++) {
138 | outNormals.add(mv.transformed(_faceNormals[i]));
139 | }
140 |
141 | List frontFaces = [];
142 |
143 | // Draw back faces
144 | paint.color = Color.lerp(const Color(0xffff0000), const Color(0x00ffffff), value + _backFaceFade.value)!;
145 | for (var i = 0; i < cubeFaces.length; i++) {
146 | final face = cubeFaces[i];
147 | final faceNormal = outNormals[i];
148 |
149 | final viewPos = view * (pos - camPos);
150 | final dot = viewPos.dot(faceNormal.xyz);
151 | if (dot < 0) {
152 | frontFaces.add(i);
153 | continue;
154 | }
155 |
156 | drawFace(canvas, paint, out, face, cx, cy);
157 | }
158 |
159 | // Draw front faces
160 | paint.color = Color.lerp(const Color(0xffff0000), const Color(0x00ffffff), value + _frontFaceFade.value)!;
161 | for (var i = 0; i < frontFaces.length; i++) {
162 | final face = cubeFaces[frontFaces[i]];
163 | drawFace(canvas, paint, out, face, cx, cy);
164 | }
165 | }
166 | }
167 | }
168 |
169 | @override
170 | void reset() {
171 | super.reset();
172 | time = 0;
173 | _backFaceFade.set(0);
174 | _frontFaceFade.set(0);
175 | _lerpValues = [];
176 | for (int i = 0; i < numCubes * numCubes; i++) {
177 | _lerpValues.add(LerpValue(1.0));
178 | }
179 | }
180 |
181 | @override
182 | void tick() {
183 | final dt = GameTime.instance.dt;
184 | time += dt;
185 |
186 | const mid = numCubes ~/ 2;
187 | bool isInRange(i, j, r) => (i >= mid - r && i <= mid + r) && (j >= mid - r && j <= mid + r);
188 | bool is1(int i, int j) => i == mid && j == mid;
189 | bool is3(int i, int j) => !is1(i, j) && isInRange(i, j, 1);
190 | bool is5(int i, int j) => !is3(i, j) && isInRange(i, j, 2);
191 | bool is7(int i, int j) => !is5(i, j) && isInRange(i, j, 3);
192 | bool is9(int i, int j) => !is7(i, j) && isInRange(i, j, 4);
193 |
194 | for (int i = 0; i < numCubes; i++) {
195 | for (int j = 0; j < numCubes; j++) {
196 | final index = i * numCubes + j;
197 | _lerpValues[index].tick(dt);
198 | if ((time > start1 && is1(i, j)) ||
199 | (time > start3 && is3(i, j)) ||
200 | (time > start5 && is5(i, j)) ||
201 | (time > start7 && is7(i, j)) ||
202 | (time > start9 && is9(i, j))) {
203 | _lerpValues[index].lerpTo(0.0, 1.0);
204 | }
205 | }
206 | }
207 |
208 | if (time > startFade) {
209 | _backFaceFade.lerpTo(0.7, 1.0);
210 | _backFaceFade.tick(dt);
211 | }
212 |
213 | if (time > fadeOut) {
214 | state = SceneState.fadeOut;
215 | _frontFaceFade.lerpTo(1.0, 1.0);
216 | _frontFaceFade.tick(dt);
217 | if (_frontFaceFade.value == 1.0) {
218 | state = SceneState.done;
219 | }
220 | }
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/lib/scenes/particle.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:flutter/material.dart' hide Colors;
4 | import 'package:pushtrix/game/game_time.dart';
5 | import 'package:pushtrix/lerp_value.dart';
6 | import 'package:pushtrix/scenes/scene.dart';
7 | import 'package:vector_math/vector_math.dart';
8 |
9 | const rotateStartAtSec = 0.2;
10 | const rotateDuration = 2.0;
11 |
12 | const swirlStartAtSec = 2;
13 | const swirlDuration = 4.0;
14 |
15 | const flickerStartsAtSec = 10;
16 | const flickerDuration = 1.0;
17 |
18 | const colorsStartAtSec = 22;
19 | const colorDuration = 2.0;
20 |
21 | const toBlackStartAtSec = 50;
22 | const toBlackDuration = 0.5;
23 |
24 | const fadeOutStartAtSec = 60;
25 |
26 | const fadeInDuration = 2;
27 | const fadeOutDuration = 2.0;
28 |
29 | class Particle {
30 | Vector3 position = Vector3.zero();
31 | Vector3 originalPosition = Vector3.zero();
32 | double originalDistance = 0.0;
33 | Vector3 targetPosition = Vector3.zero();
34 | Vector4 color = Vector4.zero();
35 | double size = 0.0;
36 | LerpValue alpha = LerpValue(0.0);
37 | }
38 |
39 | double wrap(double x, double min, double max) {
40 | return x - (max - min) * (x / (max - min)).floor();
41 | }
42 |
43 | class ParticleSystem extends Scene {
44 | List particles = [];
45 | double width = 0;
46 | double height = 0;
47 | double maxDistance = 0;
48 | double time = 0;
49 |
50 | final colorLerp = LerpValue(0);
51 | final zFlickerLerp = LerpValue(0);
52 | final rotateLerp = LerpValue(0);
53 | final swirlLerp = LerpValue(0);
54 | final goToBlack = LerpValue(1);
55 | final fadeOut = LerpValue(0);
56 |
57 | ParticleSystem({int? count}) {
58 | if (count != null) {
59 | particles = List.generate(count, (index) => Particle());
60 | }
61 |
62 | _init2dGrid();
63 | }
64 |
65 | @override
66 | void render(Canvas canvas, Size size) {
67 | final maxSide = max(size.width, size.height) * 1.3;
68 | final centerX = size.width / 2;
69 | final centerY = size.height / 2;
70 | final factorX = maxSide / width;
71 | final factorY = maxSide / height;
72 |
73 | final timeSwing = sin(time * 0.3) * 0.0015;
74 |
75 | Paint paint = Paint();
76 |
77 | var brightness = (goToBlack.value * 255).toInt();
78 | if (state == SceneState.fadeOut) {
79 | brightness = (fadeOut.value * 255).toInt();
80 | }
81 | paint.color = Color.fromRGBO(brightness, brightness, brightness, 1 - fadeOut.value);
82 | canvas.drawPaint(paint);
83 |
84 | for (var p in particles) {
85 | // Fade in
86 | if (time < fadeInDuration && p.originalDistance > (time / fadeInDuration) * maxDistance) {
87 | continue;
88 | }
89 |
90 | // fade in/out
91 | p.alpha.lerpTo(state == SceneState.fadeOut ? 0 : 1, 1.0);
92 | p.alpha.tick(GameTime.instance.dt);
93 |
94 | final angle = swirlLerp.value * timeSwing * p.originalDistance;
95 |
96 | final rot = Quaternion.euler(0, 0, angle);
97 | final pos = rot.rotated(p.position);
98 |
99 | paint.color = Color.fromRGBO(
100 | (p.color.x * 255).toInt(),
101 | (p.color.y * 255).toInt(),
102 | (p.color.z * 255).toInt(),
103 | (p.position.z + 1) * 0.75 * p.alpha.value,
104 | );
105 |
106 | double x = pos.x * factorX + centerX;
107 | double y = pos.y * factorY + centerY;
108 | double offset = (0.55 * factorX) / (pos.z * 0.8 + 1.1);
109 | double radius = p.size * offset;
110 |
111 | // canvas.save();
112 | // canvas.rotate(angle * 0.3);
113 | canvas.drawCircle(Offset(x, y), radius, paint);
114 | // canvas.restore();
115 | }
116 | }
117 |
118 | void _init2dGrid() {
119 | const widthHalf = 800.0;
120 | const heightHalf = 800.0;
121 |
122 | const dx = 60.0;
123 | const dy = 60.0;
124 |
125 | for (double x = -widthHalf; x < widthHalf; x += dx) {
126 | for (double y = -heightHalf; y < heightHalf + dy; y += dy) {
127 | final particle = Particle();
128 | particle.position.x = x;
129 | particle.position.y = y;
130 | particle.targetPosition.x = x;
131 | particle.targetPosition.y = y;
132 | particle.originalPosition.x = x;
133 | particle.originalPosition.y = y;
134 | particle.originalDistance = particle.position.distanceTo(Vector3.zero());
135 | maxDistance = max(particle.originalDistance, maxDistance);
136 |
137 | particle.size = 50.0;
138 |
139 | particles.add(particle);
140 | }
141 | }
142 |
143 | width = 2 * widthHalf;
144 | height = 2 * heightHalf;
145 | }
146 |
147 | @override
148 | void tick() {
149 | final dt = GameTime.instance.dt;
150 | time += dt;
151 | final cosine = cos(time);
152 | final sine = sin(time);
153 |
154 | for (var particle in particles) {
155 | particle.position.x = particle.originalPosition.x + (0.2 + rotateLerp.value) * cosine * 30;
156 | particle.position.y = particle.originalPosition.y + (0.2 + rotateLerp.value) * sine * 100;
157 | particle.position.z = zFlickerLerp.value * 0.3 * sin(5 * time + particle.position.x * 0.3 + particle.position.y * 0.1);
158 | Vector4 color = Vector4.zero();
159 |
160 | // Lerp between red and hsl shift
161 | Colors.hslToRgb(Vector4(sine * particle.originalDistance / 900, 1.0, 0.5, 1.0), color);
162 | particle.color.x = (1 - colorLerp.value) + (colorLerp.value) * color.x;
163 | particle.color.y = (1 - colorLerp.value) * 50 / 255 + (colorLerp.value) * color.y;
164 | particle.color.z = (1 - colorLerp.value) * 50 / 255 + (colorLerp.value) * color.z;
165 | particle.color.w = 1;
166 | }
167 |
168 | if (time < colorsStartAtSec) {
169 | colorLerp.set(0);
170 | } else {
171 | colorLerp.lerpTo(1, colorDuration);
172 | colorLerp.tick(dt);
173 | }
174 |
175 | if (time < flickerStartsAtSec) {
176 | zFlickerLerp.set(0);
177 | } else {
178 | zFlickerLerp.lerpTo(1, flickerDuration);
179 | zFlickerLerp.tick(dt);
180 | }
181 |
182 | if (time < rotateStartAtSec) {
183 | rotateLerp.set(0);
184 | } else {
185 | rotateLerp.lerpTo(0.8, rotateDuration);
186 | rotateLerp.tick(dt);
187 | }
188 |
189 | if (time < swirlStartAtSec) {
190 | swirlLerp.set(0);
191 | } else {
192 | swirlLerp.lerpTo(1, swirlDuration);
193 | swirlLerp.tick(dt);
194 | }
195 |
196 | if (time < toBlackStartAtSec) {
197 | goToBlack.set(1);
198 | } else {
199 | goToBlack.lerpTo(0, toBlackDuration);
200 | goToBlack.tick(dt);
201 | }
202 |
203 | if (time > fadeOutStartAtSec) {
204 | if (state != SceneState.fadeOut) {
205 | state = SceneState.fadeOut;
206 | }
207 |
208 | fadeOut.lerpTo(1, fadeOutDuration);
209 | fadeOut.tick(dt);
210 | if (fadeOut.value == 1) {
211 | state = SceneState.done;
212 | }
213 | }
214 | }
215 |
216 | @override
217 | void reset() {
218 | super.reset();
219 | time = 0;
220 | for (Particle p in particles) {
221 | p.alpha.set(0);
222 | }
223 | }
224 | }
225 |
--------------------------------------------------------------------------------
/lib/scenes/rays.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:pushtrix/config.dart';
5 | import 'package:pushtrix/lerp_value.dart';
6 | import 'package:pushtrix/scenes/scene.dart';
7 |
8 | import '../game/game_time.dart';
9 |
10 | const _numSections = 8;
11 | const lerpTime = 2.0;
12 |
13 | const switchFirst = 4;
14 | const switchSecond = 8;
15 | const switchThird = 12;
16 |
17 | class Rays extends Scene {
18 | LerpValue ray2Angle = LerpValue(pi / 8);
19 | LerpValue ray4Angle = LerpValue(pi / 8);
20 | LerpValue ray8Angle = LerpValue(pi / 8);
21 | double angle = 0;
22 | double radius = 0;
23 | double time = 0;
24 | final color = LerpValue(1);
25 |
26 | @override
27 | void tick() {
28 | if (!screenshotIcon) {
29 | time += GameTime.instance.dt;
30 | }
31 |
32 | angle = time * 0.4;
33 |
34 | color.lerpTo(1, 2);
35 | color.tick(GameTime.instance.dt);
36 |
37 | if (time < switchFirst) {
38 | ray2Angle.set(pi / 8);
39 | ray4Angle.set(pi / 8);
40 | ray8Angle.set(pi / 8);
41 | } else if (time < switchSecond) {
42 | ray2Angle.lerpTo(pi / 4, lerpTime);
43 | ray4Angle.lerpTo(pi / 4, lerpTime);
44 | ray8Angle.lerpTo(0, lerpTime);
45 | } else if (time < switchThird) {
46 | ray2Angle.lerpTo(pi / 2, lerpTime);
47 | ray4Angle.lerpTo(0, lerpTime);
48 | } else {
49 | ray2Angle.lerpTo(0, lerpTime);
50 |
51 | if (ray2Angle.value == 0) {
52 | state = SceneState.done;
53 | } else {
54 | state = SceneState.fadeOut;
55 | }
56 | }
57 |
58 | ray2Angle.tick(GameTime.instance.dt);
59 | ray4Angle.tick(GameTime.instance.dt);
60 | ray8Angle.tick(GameTime.instance.dt);
61 | }
62 |
63 | @override
64 | void render(Canvas canvas, Size size) {
65 | Paint paint = Paint();
66 |
67 | final cx = size.width / 2;
68 | final cy = size.height / 2;
69 | final maxSize = max(size.width, size.height);
70 |
71 | radius += GameTime.instance.dt * maxSize * 0.5;
72 | if (radius > maxSize && state == SceneState.running) {
73 | radius = 0;
74 | }
75 |
76 | final circleColor = Color.lerp(const Color(0x00ffffff), Colors.red.shade100, color.value)!;
77 | paint.color = Color.lerp(circleColor, const Color.fromARGB(0, 255, 255, 255), radius / maxSize)!;
78 | if (!screenshotIcon) {
79 | canvas.drawCircle(Offset(cx, cy), radius, paint);
80 | }
81 |
82 | const sectionDelta = 2 * pi / _numSections;
83 | paint.color = Color.lerp(const Color(0x00ffffff), Colors.red, color.value)!;
84 | for (int i = 0; i < _numSections; i++) {
85 | final angle = this.angle + i * sectionDelta;
86 | double width = 0;
87 | if (i == 0 || i == 4) {
88 | width = ray2Angle.value / 2;
89 | } else if (i == 2 || i == 6) {
90 | width = ray4Angle.value / 2;
91 | } else if (i == 1 || i == 3 || i == 5 || i == 7) {
92 | width = ray8Angle.value / 2;
93 | }
94 |
95 | if (width > 0) {
96 | final x = cx + maxSize * sin(angle - 2 * width);
97 | final y = cy + maxSize * cos(angle - 2 * width);
98 |
99 | final x2 = cx + maxSize * sin(angle);
100 | final y2 = cy + maxSize * cos(angle);
101 |
102 | var path = Path();
103 | path.moveTo(cx, cy);
104 | path.lineTo(x, y);
105 | path.lineTo(x2, y2);
106 | path.close();
107 | canvas.drawPath(path, paint);
108 | }
109 | }
110 | }
111 |
112 | @override
113 | void reset() {
114 | super.reset();
115 | time = 0;
116 | color.set(0);
117 | }
118 |
119 | @override
120 | void gameOver() {
121 | super.gameOver();
122 | color.set(1);
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/lib/scenes/scene.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/rendering.dart';
2 |
3 | enum SceneState {
4 | running,
5 | fadeOut,
6 | done,
7 | }
8 |
9 | // Base class for background scenes
10 | // A scene switches to the next one by entering
11 | // the fadeOut and then done state. Once it is done
12 | // the scene gets removed
13 | abstract class Scene {
14 | void tick();
15 | void render(Canvas canvas, Size size);
16 | void reset() {
17 | state = SceneState.running;
18 | }
19 |
20 | void gameOver() {
21 | // override if needed
22 | }
23 |
24 | SceneState state = SceneState.running;
25 | }
26 |
--------------------------------------------------------------------------------
/lib/scenes/start_screen.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:pushtrix/config.dart';
5 | import 'package:pushtrix/game/puzzle.dart';
6 | import 'package:pushtrix/scenes/scene.dart';
7 |
8 | import '../game/game_time.dart';
9 |
10 | class StartScreen extends Scene {
11 | final Puzzle _puzzle2 = Puzzle(2, 0);
12 | final Puzzle _puzzle3 = Puzzle(3, 0);
13 | final Puzzle _puzzle4 = Puzzle(4, 0);
14 | final Puzzle _puzzle5 = Puzzle(5, 0);
15 | double lastRandom2 = 0;
16 | double lastRandom3 = 0.33;
17 | double lastRandom4 = 0.66;
18 |
19 | List textPainters = [];
20 |
21 | // ATTENTION: Has to be done every frame for some reason.
22 | void cacheTextPainters() {
23 | textPainters.clear();
24 | // Cache the text painters because TextPainter.layout() is slow.
25 | for (int i = 0; i < 25; ++i) {
26 | TextSpan ts = TextSpan(
27 | style: TextStyle(
28 | fontSize: tileSize / 2,
29 | fontWeight: FontWeight.bold,
30 | fontFamily: "Rowdies",
31 | color: Colors.blue.shade800,
32 | ),
33 | text: "$i",
34 | );
35 |
36 | TextPainter tp = TextPainter(text: ts, textAlign: TextAlign.center, textDirection: TextDirection.ltr);
37 | tp.layout();
38 | textPainters.add(tp);
39 | }
40 | }
41 |
42 | @override
43 | void tick() {
44 | if (GameTime.instance.current > lastRandom2) {
45 | _puzzle2.doRandomMove();
46 | _puzzle5.doRandomMove();
47 | while (GameTime.instance.current > lastRandom2) {
48 | lastRandom2++;
49 | }
50 | }
51 |
52 | if (GameTime.instance.current > lastRandom3) {
53 | _puzzle3.doRandomMove();
54 | while (GameTime.instance.current > lastRandom3) {
55 | lastRandom3++;
56 | }
57 | }
58 | if (GameTime.instance.current > lastRandom4) {
59 | _puzzle4.doRandomMove();
60 | while (GameTime.instance.current > lastRandom4) {
61 | lastRandom4++;
62 | }
63 | }
64 |
65 | final dt = GameTime.instance.dt;
66 |
67 | _puzzle2.tickTileMoveAnim(dt);
68 | _puzzle3.tickTileMoveAnim(dt);
69 | _puzzle4.tickTileMoveAnim(dt);
70 | _puzzle5.tickTileMoveAnim(dt);
71 | }
72 |
73 | void paintPuzzle(Puzzle puzzle, Canvas canvas) {
74 | Paint paint = Paint();
75 | paint.color = Colors.blue;
76 | final sizeHalf = puzzle.screenSize / 2;
77 | canvas.drawRRect(
78 | RRect.fromLTRBR(
79 | -sizeHalf,
80 | -sizeHalf,
81 | sizeHalf,
82 | sizeHalf,
83 | const Radius.circular(tileSize / 5),
84 | ),
85 | paint,
86 | );
87 |
88 | for (int i = 1; i < puzzle.size * puzzle.size; ++i) {
89 | final offset = puzzle.getTileOffset(i);
90 | final left = offset.dx - sizeHalf;
91 | final top = offset.dy - sizeHalf;
92 | final right = left + tileSize;
93 | final bottom = top + tileSize;
94 |
95 | paint.color = Colors.white;
96 | canvas.drawRRect(
97 | RRect.fromLTRBR(
98 | left + tileBorderSize,
99 | top + tileBorderSize,
100 | right - tileBorderSize,
101 | bottom - tileBorderSize,
102 | const Radius.circular(tileSize / 5),
103 | ),
104 | paint,
105 | );
106 |
107 | TextPainter tp = textPainters[i];
108 | tp.paint(canvas, Offset(left + (tileSize - tp.width) / 2, top + (tileSize - tp.height) / 2));
109 | }
110 | }
111 |
112 | void paintPuzzleRing(Canvas canvas, Size size, Puzzle puzzle, int count, double radius, double frequency) {
113 | final cx = size.width / 2;
114 | final cy = size.height / 2;
115 | final d = 2 * pi / count;
116 | for (var i = 0; i < count; i++) {
117 | final angle = GameTime.instance.current * frequency + i * d;
118 | final x = cx + radius * cos(angle);
119 | final y = cy + radius * sin(angle);
120 | canvas.save();
121 | canvas.translate(x, y);
122 | canvas.rotate(angle + pi / 2);
123 | paintPuzzle(puzzle, canvas);
124 | canvas.restore();
125 | }
126 | }
127 |
128 | void paintRays(Canvas canvas, Size size) {
129 | Paint paint = Paint();
130 | paint.color = Colors.red;
131 |
132 | final cx = size.width / 2;
133 | final cy = size.height / 2;
134 | final maxSize = max(size.width, size.height);
135 |
136 | const numSections = 10;
137 | const sectionDelta = 2 * pi / numSections;
138 |
139 | for (int i = 0; i < numSections; i++) {
140 | final angle = GameTime.instance.current * 0.2 + i * sectionDelta;
141 | final x = cx + maxSize * sin(angle);
142 | final y = cy + maxSize * cos(angle);
143 |
144 | final x2 = cx + maxSize * sin(angle + sectionDelta / 2);
145 | final y2 = cy + maxSize * cos(angle + sectionDelta / 2);
146 |
147 | var path = Path();
148 | path.moveTo(cx, cy);
149 | path.lineTo(x, y);
150 | path.lineTo(x2, y2);
151 | path.close();
152 | canvas.drawPath(path, paint);
153 | }
154 | }
155 |
156 | @override
157 | void render(Canvas canvas, Size size) {
158 | cacheTextPainters();
159 |
160 | paintRays(canvas, size);
161 |
162 | const num2 = 6;
163 | final r2 = _puzzle2.screenSize * 1.2;
164 | const f2 = 0.3;
165 |
166 | const num3 = 10;
167 | final r3 = r2 + _puzzle3.screenSize * 1;
168 | const f3 = 0.2;
169 |
170 | const num4 = 13;
171 | final r4 = r3 + _puzzle4.screenSize * 1;
172 | const f4 = 0.1;
173 |
174 | const num5 = 15;
175 | final r5 = r4 + _puzzle5.screenSize * 1;
176 | const f5 = 0.1;
177 |
178 | paintPuzzleRing(canvas, size, _puzzle2, num2, r2, f2);
179 | paintPuzzleRing(canvas, size, _puzzle3, num3, r3, f3);
180 |
181 | final maxSize = max(size.width, size.height);
182 | if (maxSize > r4 + _puzzle4.screenSize) {
183 | paintPuzzleRing(canvas, size, _puzzle4, num4, r4, f4);
184 | }
185 |
186 | if (maxSize > r5 + _puzzle5.screenSize) {
187 | paintPuzzleRing(canvas, size, _puzzle5, num5, r5, f5);
188 | }
189 |
190 | canvas.drawRect(Rect.fromLTRB(0, 0, size.width, size.height), Paint()..color = Colors.white.withOpacity(0.4));
191 | }
192 |
193 | @override
194 | void reset() {
195 | // pass
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/lib/state_transition.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter/scheduler.dart';
5 | import 'package:pushtrix/game/game.dart';
6 |
7 | enum TransitionState {
8 | stateChange,
9 | finished,
10 | }
11 |
12 | class TransitionPainter extends CustomPainter {
13 | final double value;
14 | final bool closed;
15 |
16 | TransitionPainter({required this.value, required this.closed});
17 |
18 | @override
19 | void paint(Canvas canvas, Size size) {
20 | final center = Offset(size.width / 2, size.height / 2);
21 | // below one is big circle and instead of this circle you can draw your shape here.
22 | final paint = Paint()
23 | ..color = Colors.blue.shade800
24 | ..strokeWidth = 5
25 | ..strokeCap = StrokeCap.round;
26 |
27 | final Path path = Path();
28 | path.fillType = PathFillType.evenOdd;
29 | path.addRect(Rect.fromLTWH(0, 0, size.width, size.height));
30 | final radius = !closed ? max(0, 1 - value) : value - 1;
31 | path.addOval(Rect.fromCircle(center: center, radius: radius * max(size.width, size.height)));
32 | canvas.drawPath(path, paint);
33 | }
34 |
35 | @override
36 | bool shouldRepaint(CustomPainter oldDelegate) => true;
37 | }
38 |
39 | class StateTransition extends StatefulWidget {
40 | final Function(TransitionState state) onTransition;
41 | const StateTransition(this.onTransition, {Key? key}) : super(key: key);
42 |
43 | @override
44 | State createState() => _StateTransitionState();
45 | }
46 |
47 | class _StateTransitionState extends State {
48 | late final Ticker ticker;
49 | double time = 0;
50 | double dt = 0;
51 | double value = 0;
52 | bool _isClosed = false;
53 |
54 | _StateTransitionState() {
55 | ticker = Ticker((duration) {
56 | final current = duration.inMilliseconds / 1000.0;
57 | setState(() {
58 | dt = current - time;
59 | time = current;
60 |
61 | // Calculate seconds passed since the start of the animation.
62 | value = (DateTime.now().millisecondsSinceEpoch - (Game.instance.transitionStarted ?? 0)) / 1000;
63 | if (value > 1 && !_isClosed) {
64 | _isClosed = true;
65 | widget.onTransition(TransitionState.stateChange);
66 | }
67 |
68 | if (value > 2) {
69 | debugPrint("transition finished");
70 | ticker.stop();
71 | Game.instance.transitionStarted = null;
72 | widget.onTransition(TransitionState.finished);
73 | }
74 | });
75 | });
76 |
77 | ticker.start();
78 | }
79 |
80 | @override
81 | void dispose() {
82 | debugPrint("_StateTransitionState dispose");
83 | super.dispose();
84 | ticker.dispose();
85 | }
86 |
87 | @override
88 | Widget build(BuildContext context) {
89 | final size = MediaQuery.of(context).size;
90 | return CustomPaint(
91 | size: size,
92 | painter: TransitionPainter(value: value, closed: _isClosed),
93 | );
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/macos/.gitignore:
--------------------------------------------------------------------------------
1 | # Flutter-related
2 | **/Flutter/ephemeral/
3 | **/Pods/
4 |
5 | # Xcode-related
6 | **/dgph
7 | **/xcuserdata/
8 |
--------------------------------------------------------------------------------
/macos/Flutter/Flutter-Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "ephemeral/Flutter-Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/macos/Flutter/Flutter-Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "ephemeral/Flutter-Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/macos/Flutter/GeneratedPluginRegistrant.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | import FlutterMacOS
6 | import Foundation
7 |
8 | import app_links_macos
9 | import audioplayers_darwin
10 | import path_provider_macos
11 | import shared_preferences_macos
12 | import url_launcher_macos
13 |
14 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
15 | AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
16 | AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
17 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
18 | SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
19 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
20 | }
21 |
--------------------------------------------------------------------------------
/macos/Podfile:
--------------------------------------------------------------------------------
1 | platform :osx, '10.12'
2 |
3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
5 |
6 | project 'Runner', {
7 | 'Debug' => :debug,
8 | 'Profile' => :release,
9 | 'Release' => :release,
10 | }
11 |
12 | def flutter_root
13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
14 | unless File.exist?(generated_xcode_build_settings_path)
15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
16 | end
17 |
18 | File.foreach(generated_xcode_build_settings_path) do |line|
19 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
20 | return matches[1].strip if matches
21 | end
22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
23 | end
24 |
25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
26 |
27 | flutter_macos_podfile_setup
28 |
29 | target 'Runner' do
30 | use_frameworks!
31 | use_modular_headers!
32 |
33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
34 | end
35 |
36 | post_install do |installer|
37 | installer.pods_project.targets.each do |target|
38 | flutter_additional_macos_build_settings(target)
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/macos/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - app_links_macos (1.0.0):
3 | - FlutterMacOS
4 | - audioplayers_darwin (0.0.1):
5 | - FlutterMacOS
6 | - FlutterMacOS (1.0.0)
7 | - path_provider_macos (0.0.1):
8 | - FlutterMacOS
9 | - shared_preferences_macos (0.0.1):
10 | - FlutterMacOS
11 | - url_launcher_macos (0.0.1):
12 | - FlutterMacOS
13 |
14 | DEPENDENCIES:
15 | - app_links_macos (from `Flutter/ephemeral/.symlinks/plugins/app_links_macos/macos`)
16 | - audioplayers_darwin (from `Flutter/ephemeral/.symlinks/plugins/audioplayers_darwin/macos`)
17 | - FlutterMacOS (from `Flutter/ephemeral`)
18 | - path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`)
19 | - shared_preferences_macos (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_macos/macos`)
20 | - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
21 |
22 | EXTERNAL SOURCES:
23 | app_links_macos:
24 | :path: Flutter/ephemeral/.symlinks/plugins/app_links_macos/macos
25 | audioplayers_darwin:
26 | :path: Flutter/ephemeral/.symlinks/plugins/audioplayers_darwin/macos
27 | FlutterMacOS:
28 | :path: Flutter/ephemeral
29 | path_provider_macos:
30 | :path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos
31 | shared_preferences_macos:
32 | :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_macos/macos
33 | url_launcher_macos:
34 | :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
35 |
36 | SPEC CHECKSUMS:
37 | app_links_macos: 15e554f46b367713cb8d1db37cee3d1701d22a27
38 | audioplayers_darwin: dcad41de4fbd0099cb3749f7ab3b0cb8f70b810c
39 | FlutterMacOS: ae6af50a8ea7d6103d888583d46bd8328a7e9811
40 | path_provider_macos: 3c0c3b4b0d4a76d2bf989a913c2de869c5641a19
41 | shared_preferences_macos: a64dc611287ed6cbe28fd1297898db1336975727
42 | url_launcher_macos: 597e05b8e514239626bcf4a850fcf9ef5c856ec3
43 |
44 | PODFILE CHECKSUM: c7161fcf45d4fd9025dc0f48a76d6e64e52f8176
45 |
46 | COCOAPODS: 1.11.2
47 |
--------------------------------------------------------------------------------
/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/macos/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/macos/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import FlutterMacOS
3 |
4 | @NSApplicationMain
5 | class AppDelegate: FlutterAppDelegate {
6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
7 | return true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info": {
3 | "version": 1,
4 | "author": "xcode"
5 | },
6 | "images": [
7 | {
8 | "size": "16x16",
9 | "idiom": "mac",
10 | "filename": "app_icon_16.png",
11 | "scale": "1x"
12 | },
13 | {
14 | "size": "16x16",
15 | "idiom": "mac",
16 | "filename": "app_icon_32.png",
17 | "scale": "2x"
18 | },
19 | {
20 | "size": "32x32",
21 | "idiom": "mac",
22 | "filename": "app_icon_32.png",
23 | "scale": "1x"
24 | },
25 | {
26 | "size": "32x32",
27 | "idiom": "mac",
28 | "filename": "app_icon_64.png",
29 | "scale": "2x"
30 | },
31 | {
32 | "size": "128x128",
33 | "idiom": "mac",
34 | "filename": "app_icon_128.png",
35 | "scale": "1x"
36 | },
37 | {
38 | "size": "128x128",
39 | "idiom": "mac",
40 | "filename": "app_icon_256.png",
41 | "scale": "2x"
42 | },
43 | {
44 | "size": "256x256",
45 | "idiom": "mac",
46 | "filename": "app_icon_256.png",
47 | "scale": "1x"
48 | },
49 | {
50 | "size": "256x256",
51 | "idiom": "mac",
52 | "filename": "app_icon_512.png",
53 | "scale": "2x"
54 | },
55 | {
56 | "size": "512x512",
57 | "idiom": "mac",
58 | "filename": "app_icon_512.png",
59 | "scale": "1x"
60 | },
61 | {
62 | "size": "512x512",
63 | "idiom": "mac",
64 | "filename": "app_icon_1024.png",
65 | "scale": "2x"
66 | }
67 | ]
68 | }
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
--------------------------------------------------------------------------------
/macos/Runner/Configs/AppInfo.xcconfig:
--------------------------------------------------------------------------------
1 | // Application-level settings for the Runner target.
2 | //
3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the
4 | // future. If not, the values below would default to using the project name when this becomes a
5 | // 'flutter create' template.
6 |
7 | // The application's name. By default this is also the title of the Flutter window.
8 | PRODUCT_NAME = pushtrix
9 |
10 | // The application's bundle identifier
11 | PRODUCT_BUNDLE_IDENTIFIER = com.chengine.pushtrix
12 |
13 | // The copyright displayed in application information
14 | PRODUCT_COPYRIGHT = Copyright © 2022 com.chengine. All rights reserved.
15 |
--------------------------------------------------------------------------------
/macos/Runner/Configs/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "../../Flutter/Flutter-Debug.xcconfig"
2 | #include "Warnings.xcconfig"
3 |
--------------------------------------------------------------------------------
/macos/Runner/Configs/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "../../Flutter/Flutter-Release.xcconfig"
2 | #include "Warnings.xcconfig"
3 |
--------------------------------------------------------------------------------
/macos/Runner/Configs/Warnings.xcconfig:
--------------------------------------------------------------------------------
1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings
2 | GCC_WARN_UNDECLARED_SELECTOR = YES
3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES
4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE
5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
6 | CLANG_WARN_PRAGMA_PACK = YES
7 | CLANG_WARN_STRICT_PROTOTYPES = YES
8 | CLANG_WARN_COMMA = YES
9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES
10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES
11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
12 | GCC_WARN_SHADOW = YES
13 | CLANG_WARN_UNREACHABLE_CODE = YES
14 |
--------------------------------------------------------------------------------
/macos/Runner/DebugProfile.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.cs.allow-jit
8 |
9 | com.apple.security.network.server
10 |
11 | com.apple.security.network.client
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/macos/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | NSHumanReadableCopyright
26 | $(PRODUCT_COPYRIGHT)
27 | NSMainNibFile
28 | MainMenu
29 | NSPrincipalClass
30 | NSApplication
31 |
32 |
33 |
--------------------------------------------------------------------------------
/macos/Runner/MainFlutterWindow.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import FlutterMacOS
3 |
4 | class MainFlutterWindow: NSWindow {
5 | override func awakeFromNib() {
6 | let flutterViewController = FlutterViewController.init()
7 | let windowFrame = self.frame
8 | self.contentViewController = flutterViewController
9 | self.setFrame(windowFrame, display: true)
10 |
11 | RegisterGeneratedPlugins(registry: flutterViewController)
12 |
13 | super.awakeFromNib()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/macos/Runner/Release.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.network.client
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: pushtrix
2 | description: A fast paced puzzle game.
3 |
4 | # The following line prevents the package from being accidentally published to
5 | # pub.dev using `flutter pub publish`. This is preferred for private packages.
6 | publish_to: "none" # Remove this line if you wish to publish to pub.dev
7 |
8 | # The following defines the version and build number for your application.
9 | # A version number is three numbers separated by dots, like 1.2.43
10 | # followed by an optional build number separated by a +.
11 | # Both the version and the builder number may be overridden in flutter
12 | # build by specifying --build-name and --build-number, respectively.
13 | # In Android, build-name is used as versionName while build-number used as versionCode.
14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
16 | # Read more about iOS versioning at
17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
18 | version: 1.0.0+1
19 |
20 | environment:
21 | sdk: ">=2.15.1 <3.0.0"
22 |
23 | # Dependencies specify other packages that your package needs in order to work.
24 | # To automatically upgrade your package dependencies to the latest versions
25 | # consider running `flutter pub upgrade --major-versions`. Alternatively,
26 | # dependencies can be manually updated by changing the version numbers below to
27 | # the latest version available on pub.dev. To see which dependencies have newer
28 | # versions available, run `flutter pub outdated`.
29 | dependencies:
30 | flutter:
31 | sdk: flutter
32 |
33 | # The following adds the Cupertino Icons font to your application.
34 | # Use with the CupertinoIcons class for iOS style icons.
35 | cupertino_icons: ^1.0.2
36 | animated_button: ^0.2.0
37 | shared_preferences: ^2.0.13
38 | flame_audio: ^1.3.0
39 | supabase_flutter: ^1.0.1
40 | eval_ex: ^1.1.7
41 | envied: ^0.2.3
42 |
43 | dev_dependencies:
44 | flutter_test:
45 | sdk: flutter
46 |
47 | # The "flutter_lints" package below contains a set of recommended lints to
48 | # encourage good coding practices. The lint set provided by the package is
49 | # activated in the `analysis_options.yaml` file located at the root of your
50 | # package. See that file for information about deactivating specific lint
51 | # rules and activating additional ones.
52 | flutter_lints: ^1.0.0
53 | build_runner: ^2.3.2
54 | envied_generator: ^0.2.3
55 | flutter_launcher_icons: ^0.11.0
56 |
57 | # For information on the generic Dart part of this file, see the
58 | # following page: https://dart.dev/tools/pub/pubspec
59 |
60 | flutter_icons:
61 | ios: true
62 | image_path_ios: "assets/launcher/icon.png"
63 | android: true
64 | image_path_android: "assets/launcher/icon_rounded.png"
65 | web:
66 | generate: true
67 | image_path: "assets/launcher/icon_rounded.png"
68 | #background_color: "#hexcode"
69 | #theme_color: "#hexcode"
70 | windows:
71 | generate: true
72 | image_path: "assets/launcher/icon_rounded.png"
73 | icon_size: 256 # min:48, max:256, default: 48
74 | macos:
75 | generate: true
76 | image_path: "assets/launcher/icon.png"
77 |
78 | # The following section is specific to Flutter.
79 | flutter:
80 | # The following line ensures that the Material Icons font is
81 | # included with your application, so that you can use the icons in
82 | # the material Icons class.
83 | uses-material-design: true
84 |
85 | # To add assets to your application, add an assets section, like this:
86 | assets:
87 | - assets/audio/sounds/click.wav
88 | - assets/audio/sounds/swish.wav
89 | - assets/audio/sounds/excellent.mp3
90 | - assets/audio/sounds/great.mp3
91 | - assets/audio/sounds/keep_it_up.mp3
92 | - assets/audio/sounds/well_done.mp3
93 | - assets/audio/sounds/awesome.mp3
94 | - assets/audio/sounds/game_over.mp3
95 | - assets/audio/sounds/that_was_close.mp3
96 | - assets/audio/sounds/too_easy.mp3
97 | - assets/audio/sounds/beep.mp3
98 | - assets/audio/sounds/beep_long.mp3
99 | - assets/audio/music/bensound-extremeaction.mp3
100 | - assets/audio/music/bensound-punky.mp3
101 | - assets/audio/music/bensound-scifi.mp3
102 |
103 | fonts:
104 | - family: Rowdies
105 | fonts:
106 | - asset: fonts/Rowdies-Regular.ttf
107 | weight: 400
108 | - asset: fonts/Rowdies-Light.ttf
109 | weight: 300
110 | - asset: fonts/Rowdies-Bold.ttf
111 | weight: 700
112 | - family: AzeretMono
113 | fonts:
114 | - asset: fonts/AzeretMono-Bold.ttf
115 | weight: 700
116 |
117 | # derry scripts
118 | scripts:
119 | build_runner: flutter pub run build_runner build --delete-conflicting-outputs
120 | build_launcher_icons: flutter pub run flutter_launcher_icons
121 | build:
122 | web:
123 | - flutter build web --base-href /pushtrix/ --web-renderer canvaskit
124 | - echo "Now copy ..."
125 | - cp -a build/web/. ../pushtrix/docs/
126 | ios:
127 | - flutter build ipa --obfuscate --split-debug-info=ios/debug_info
128 |
--------------------------------------------------------------------------------
/source_assets/loading/loading_00.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/source_assets/loading/loading_00.png
--------------------------------------------------------------------------------
/source_assets/loading/loading_00_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/source_assets/loading/loading_00_small.png
--------------------------------------------------------------------------------
/source_assets/loading/loading_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/source_assets/loading/loading_01.png
--------------------------------------------------------------------------------
/source_assets/loading/loading_01_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/source_assets/loading/loading_01_small.png
--------------------------------------------------------------------------------
/source_assets/loading/loading_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/source_assets/loading/loading_02.png
--------------------------------------------------------------------------------
/source_assets/loading/loading_02_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/source_assets/loading/loading_02_small.png
--------------------------------------------------------------------------------
/source_assets/loading/loading_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/source_assets/loading/loading_03.png
--------------------------------------------------------------------------------
/source_assets/loading/loading_03_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/source_assets/loading/loading_03_small.png
--------------------------------------------------------------------------------
/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility that Flutter provides. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | import 'package:flutter/material.dart';
9 | import 'package:flutter_test/flutter_test.dart';
10 |
11 | import 'package:pushtrix/main.dart';
12 |
13 | void main() {
14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
15 | // Build our app and trigger a frame.
16 | await tester.pumpWidget(const App());
17 |
18 | // Verify that our counter starts at 0.
19 | expect(find.text('0'), findsOneWidget);
20 | expect(find.text('1'), findsNothing);
21 |
22 | // Tap the '+' icon and trigger a frame.
23 | await tester.tap(find.byIcon(Icons.add));
24 | await tester.pump();
25 |
26 | // Verify that our counter has incremented.
27 | expect(find.text('0'), findsNothing);
28 | expect(find.text('1'), findsOneWidget);
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/web/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/web/favicon.png
--------------------------------------------------------------------------------
/web/icons/Icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/web/icons/Icon-192.png
--------------------------------------------------------------------------------
/web/icons/Icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/web/icons/Icon-512.png
--------------------------------------------------------------------------------
/web/icons/Icon-maskable-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/web/icons/Icon-maskable-192.png
--------------------------------------------------------------------------------
/web/icons/Icon-maskable-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/web/icons/Icon-maskable-512.png
--------------------------------------------------------------------------------
/web/img/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/web/img/loading.gif
--------------------------------------------------------------------------------
/web/img/loading_small.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/web/img/loading_small.gif
--------------------------------------------------------------------------------
/web/img/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/web/img/splash.png
--------------------------------------------------------------------------------
/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | Pushtrix
33 |
34 |
35 |
36 |
51 |
54 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/web/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Pushtrix",
3 | "short_name": "Pushtrix",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "background_color": "#0175C2",
7 | "theme_color": "#0175C2",
8 | "description": "A fast paced puzzle game.",
9 | "orientation": "portrait-primary",
10 | "prefer_related_applications": false,
11 | "icons": [
12 | {
13 | "src": "icons/Icon-192.png",
14 | "sizes": "192x192",
15 | "type": "image/png"
16 | },
17 | {
18 | "src": "icons/Icon-512.png",
19 | "sizes": "512x512",
20 | "type": "image/png"
21 | },
22 | {
23 | "src": "icons/Icon-maskable-192.png",
24 | "sizes": "192x192",
25 | "type": "image/png",
26 | "purpose": "maskable"
27 | },
28 | {
29 | "src": "icons/Icon-maskable-512.png",
30 | "sizes": "512x512",
31 | "type": "image/png",
32 | "purpose": "maskable"
33 | }
34 | ]
35 | }
--------------------------------------------------------------------------------
/windows/.gitignore:
--------------------------------------------------------------------------------
1 | flutter/ephemeral/
2 |
3 | # Visual Studio user-specific files.
4 | *.suo
5 | *.user
6 | *.userosscache
7 | *.sln.docstates
8 |
9 | # Visual Studio build-related files.
10 | x64/
11 | x86/
12 |
13 | # Visual Studio cache files
14 | # files ending in .cache can be ignored
15 | *.[Cc]ache
16 | # but keep track of directories ending in .cache
17 | !*.[Cc]ache/
18 |
--------------------------------------------------------------------------------
/windows/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # Project-level configuration.
2 | cmake_minimum_required(VERSION 3.14)
3 | project(flutter_puzzle LANGUAGES CXX)
4 |
5 | # The name of the executable created for the application. Change this to change
6 | # the on-disk name of your application.
7 | set(BINARY_NAME "flutter_puzzle")
8 |
9 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent
10 | # versions of CMake.
11 | cmake_policy(SET CMP0063 NEW)
12 |
13 | # Define build configuration option.
14 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
15 | if(IS_MULTICONFIG)
16 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
17 | CACHE STRING "" FORCE)
18 | else()
19 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
20 | set(CMAKE_BUILD_TYPE "Debug" CACHE
21 | STRING "Flutter build mode" FORCE)
22 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
23 | "Debug" "Profile" "Release")
24 | endif()
25 | endif()
26 | # Define settings for the Profile build mode.
27 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
28 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
29 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
30 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
31 |
32 | # Use Unicode for all projects.
33 | add_definitions(-DUNICODE -D_UNICODE)
34 |
35 | # Compilation settings that should be applied to most targets.
36 | #
37 | # Be cautious about adding new options here, as plugins use this function by
38 | # default. In most cases, you should add new options to specific targets instead
39 | # of modifying this function.
40 | function(APPLY_STANDARD_SETTINGS TARGET)
41 | target_compile_features(${TARGET} PUBLIC cxx_std_17)
42 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
43 | target_compile_options(${TARGET} PRIVATE /EHsc)
44 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
45 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>")
46 | endfunction()
47 |
48 | # Flutter library and tool build rules.
49 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
50 | add_subdirectory(${FLUTTER_MANAGED_DIR})
51 |
52 | # Application build; see runner/CMakeLists.txt.
53 | add_subdirectory("runner")
54 |
55 | # Generated plugin build rules, which manage building the plugins and adding
56 | # them to the application.
57 | include(flutter/generated_plugins.cmake)
58 |
59 |
60 | # === Installation ===
61 | # Support files are copied into place next to the executable, so that it can
62 | # run in place. This is done instead of making a separate bundle (as on Linux)
63 | # so that building and running from within Visual Studio will work.
64 | set(BUILD_BUNDLE_DIR "$")
65 | # Make the "install" step default, as it's required to run.
66 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
67 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
68 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
69 | endif()
70 |
71 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
72 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
73 |
74 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
75 | COMPONENT Runtime)
76 |
77 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
78 | COMPONENT Runtime)
79 |
80 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
81 | COMPONENT Runtime)
82 |
83 | if(PLUGIN_BUNDLED_LIBRARIES)
84 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
85 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
86 | COMPONENT Runtime)
87 | endif()
88 |
89 | # Fully re-copy the assets directory on each build to avoid having stale files
90 | # from a previous install.
91 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
92 | install(CODE "
93 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
94 | " COMPONENT Runtime)
95 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
96 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
97 |
98 | # Install the AOT library on non-Debug builds only.
99 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
100 | CONFIGURATIONS Profile;Release
101 | COMPONENT Runtime)
102 |
--------------------------------------------------------------------------------
/windows/flutter/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # This file controls Flutter-level build steps. It should not be edited.
2 | cmake_minimum_required(VERSION 3.14)
3 |
4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
5 |
6 | # Configuration provided via flutter tool.
7 | include(${EPHEMERAL_DIR}/generated_config.cmake)
8 |
9 | # TODO: Move the rest of this into files in ephemeral. See
10 | # https://github.com/flutter/flutter/issues/57146.
11 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
12 |
13 | # === Flutter Library ===
14 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
15 |
16 | # Published to parent scope for install step.
17 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
18 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
19 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
20 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
21 |
22 | list(APPEND FLUTTER_LIBRARY_HEADERS
23 | "flutter_export.h"
24 | "flutter_windows.h"
25 | "flutter_messenger.h"
26 | "flutter_plugin_registrar.h"
27 | "flutter_texture_registrar.h"
28 | )
29 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
30 | add_library(flutter INTERFACE)
31 | target_include_directories(flutter INTERFACE
32 | "${EPHEMERAL_DIR}"
33 | )
34 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
35 | add_dependencies(flutter flutter_assemble)
36 |
37 | # === Wrapper ===
38 | list(APPEND CPP_WRAPPER_SOURCES_CORE
39 | "core_implementations.cc"
40 | "standard_codec.cc"
41 | )
42 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
43 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
44 | "plugin_registrar.cc"
45 | )
46 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
47 | list(APPEND CPP_WRAPPER_SOURCES_APP
48 | "flutter_engine.cc"
49 | "flutter_view_controller.cc"
50 | )
51 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
52 |
53 | # Wrapper sources needed for a plugin.
54 | add_library(flutter_wrapper_plugin STATIC
55 | ${CPP_WRAPPER_SOURCES_CORE}
56 | ${CPP_WRAPPER_SOURCES_PLUGIN}
57 | )
58 | apply_standard_settings(flutter_wrapper_plugin)
59 | set_target_properties(flutter_wrapper_plugin PROPERTIES
60 | POSITION_INDEPENDENT_CODE ON)
61 | set_target_properties(flutter_wrapper_plugin PROPERTIES
62 | CXX_VISIBILITY_PRESET hidden)
63 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
64 | target_include_directories(flutter_wrapper_plugin PUBLIC
65 | "${WRAPPER_ROOT}/include"
66 | )
67 | add_dependencies(flutter_wrapper_plugin flutter_assemble)
68 |
69 | # Wrapper sources needed for the runner.
70 | add_library(flutter_wrapper_app STATIC
71 | ${CPP_WRAPPER_SOURCES_CORE}
72 | ${CPP_WRAPPER_SOURCES_APP}
73 | )
74 | apply_standard_settings(flutter_wrapper_app)
75 | target_link_libraries(flutter_wrapper_app PUBLIC flutter)
76 | target_include_directories(flutter_wrapper_app PUBLIC
77 | "${WRAPPER_ROOT}/include"
78 | )
79 | add_dependencies(flutter_wrapper_app flutter_assemble)
80 |
81 | # === Flutter tool backend ===
82 | # _phony_ is a non-existent file to force this command to run every time,
83 | # since currently there's no way to get a full input/output list from the
84 | # flutter tool.
85 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
86 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
87 | add_custom_command(
88 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
89 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
90 | ${CPP_WRAPPER_SOURCES_APP}
91 | ${PHONY_OUTPUT}
92 | COMMAND ${CMAKE_COMMAND} -E env
93 | ${FLUTTER_TOOL_ENVIRONMENT}
94 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
95 | windows-x64 $
96 | VERBATIM
97 | )
98 | add_custom_target(flutter_assemble DEPENDS
99 | "${FLUTTER_LIBRARY}"
100 | ${FLUTTER_LIBRARY_HEADERS}
101 | ${CPP_WRAPPER_SOURCES_CORE}
102 | ${CPP_WRAPPER_SOURCES_PLUGIN}
103 | ${CPP_WRAPPER_SOURCES_APP}
104 | )
105 |
--------------------------------------------------------------------------------
/windows/flutter/generated_plugin_registrant.cc:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | // clang-format off
6 |
7 | #include "generated_plugin_registrant.h"
8 |
9 | #include
10 | #include
11 | #include
12 |
13 | void RegisterPlugins(flutter::PluginRegistry* registry) {
14 | AppLinksWindowsPluginRegisterWithRegistrar(
15 | registry->GetRegistrarForPlugin("AppLinksWindowsPlugin"));
16 | AudioplayersWindowsPluginRegisterWithRegistrar(
17 | registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin"));
18 | UrlLauncherWindowsRegisterWithRegistrar(
19 | registry->GetRegistrarForPlugin("UrlLauncherWindows"));
20 | }
21 |
--------------------------------------------------------------------------------
/windows/flutter/generated_plugin_registrant.h:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | // clang-format off
6 |
7 | #ifndef GENERATED_PLUGIN_REGISTRANT_
8 | #define GENERATED_PLUGIN_REGISTRANT_
9 |
10 | #include
11 |
12 | // Registers Flutter plugins.
13 | void RegisterPlugins(flutter::PluginRegistry* registry);
14 |
15 | #endif // GENERATED_PLUGIN_REGISTRANT_
16 |
--------------------------------------------------------------------------------
/windows/flutter/generated_plugins.cmake:
--------------------------------------------------------------------------------
1 | #
2 | # Generated file, do not edit.
3 | #
4 |
5 | list(APPEND FLUTTER_PLUGIN_LIST
6 | app_links_windows
7 | audioplayers_windows
8 | url_launcher_windows
9 | )
10 |
11 | list(APPEND FLUTTER_FFI_PLUGIN_LIST
12 | )
13 |
14 | set(PLUGIN_BUNDLED_LIBRARIES)
15 |
16 | foreach(plugin ${FLUTTER_PLUGIN_LIST})
17 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
18 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
19 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $)
20 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
21 | endforeach(plugin)
22 |
23 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
24 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
25 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
26 | endforeach(ffi_plugin)
27 |
--------------------------------------------------------------------------------
/windows/runner/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.14)
2 | project(runner LANGUAGES CXX)
3 |
4 | # Define the application target. To change its name, change BINARY_NAME in the
5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
6 | # work.
7 | #
8 | # Any new source files that you add to the application should be added here.
9 | add_executable(${BINARY_NAME} WIN32
10 | "flutter_window.cpp"
11 | "main.cpp"
12 | "utils.cpp"
13 | "win32_window.cpp"
14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
15 | "Runner.rc"
16 | "runner.exe.manifest"
17 | )
18 |
19 | # Apply the standard set of build settings. This can be removed for applications
20 | # that need different build settings.
21 | apply_standard_settings(${BINARY_NAME})
22 |
23 | # Disable Windows macros that collide with C++ standard library functions.
24 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
25 |
26 | # Add dependency libraries and include directories. Add any application-specific
27 | # dependencies here.
28 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
29 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
30 |
31 | # Run the Flutter tool portions of the build. This must not be removed.
32 | add_dependencies(${BINARY_NAME} flutter_assemble)
33 |
--------------------------------------------------------------------------------
/windows/runner/Runner.rc:
--------------------------------------------------------------------------------
1 | // Microsoft Visual C++ generated resource script.
2 | //
3 | #pragma code_page(65001)
4 | #include "resource.h"
5 |
6 | #define APSTUDIO_READONLY_SYMBOLS
7 | /////////////////////////////////////////////////////////////////////////////
8 | //
9 | // Generated from the TEXTINCLUDE 2 resource.
10 | //
11 | #include "winres.h"
12 |
13 | /////////////////////////////////////////////////////////////////////////////
14 | #undef APSTUDIO_READONLY_SYMBOLS
15 |
16 | /////////////////////////////////////////////////////////////////////////////
17 | // English (United States) resources
18 |
19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
21 |
22 | #ifdef APSTUDIO_INVOKED
23 | /////////////////////////////////////////////////////////////////////////////
24 | //
25 | // TEXTINCLUDE
26 | //
27 |
28 | 1 TEXTINCLUDE
29 | BEGIN
30 | "resource.h\0"
31 | END
32 |
33 | 2 TEXTINCLUDE
34 | BEGIN
35 | "#include ""winres.h""\r\n"
36 | "\0"
37 | END
38 |
39 | 3 TEXTINCLUDE
40 | BEGIN
41 | "\r\n"
42 | "\0"
43 | END
44 |
45 | #endif // APSTUDIO_INVOKED
46 |
47 |
48 | /////////////////////////////////////////////////////////////////////////////
49 | //
50 | // Icon
51 | //
52 |
53 | // Icon with lowest ID value placed first to ensure application icon
54 | // remains consistent on all systems.
55 | IDI_APP_ICON ICON "resources\\app_icon.ico"
56 |
57 |
58 | /////////////////////////////////////////////////////////////////////////////
59 | //
60 | // Version
61 | //
62 |
63 | #ifdef FLUTTER_BUILD_NUMBER
64 | #define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER
65 | #else
66 | #define VERSION_AS_NUMBER 1,0,0
67 | #endif
68 |
69 | #ifdef FLUTTER_BUILD_NAME
70 | #define VERSION_AS_STRING #FLUTTER_BUILD_NAME
71 | #else
72 | #define VERSION_AS_STRING "1.0.0"
73 | #endif
74 |
75 | VS_VERSION_INFO VERSIONINFO
76 | FILEVERSION VERSION_AS_NUMBER
77 | PRODUCTVERSION VERSION_AS_NUMBER
78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
79 | #ifdef _DEBUG
80 | FILEFLAGS VS_FF_DEBUG
81 | #else
82 | FILEFLAGS 0x0L
83 | #endif
84 | FILEOS VOS__WINDOWS32
85 | FILETYPE VFT_APP
86 | FILESUBTYPE 0x0L
87 | BEGIN
88 | BLOCK "StringFileInfo"
89 | BEGIN
90 | BLOCK "040904e4"
91 | BEGIN
92 | VALUE "CompanyName", "com.chengine" "\0"
93 | VALUE "FileDescription", "flutter_puzzle" "\0"
94 | VALUE "FileVersion", VERSION_AS_STRING "\0"
95 | VALUE "InternalName", "flutter_puzzle" "\0"
96 | VALUE "LegalCopyright", "Copyright (C) 2022 com.chengine. All rights reserved." "\0"
97 | VALUE "OriginalFilename", "flutter_puzzle.exe" "\0"
98 | VALUE "ProductName", "flutter_puzzle" "\0"
99 | VALUE "ProductVersion", VERSION_AS_STRING "\0"
100 | END
101 | END
102 | BLOCK "VarFileInfo"
103 | BEGIN
104 | VALUE "Translation", 0x409, 1252
105 | END
106 | END
107 |
108 | #endif // English (United States) resources
109 | /////////////////////////////////////////////////////////////////////////////
110 |
111 |
112 |
113 | #ifndef APSTUDIO_INVOKED
114 | /////////////////////////////////////////////////////////////////////////////
115 | //
116 | // Generated from the TEXTINCLUDE 3 resource.
117 | //
118 |
119 |
120 | /////////////////////////////////////////////////////////////////////////////
121 | #endif // not APSTUDIO_INVOKED
122 |
--------------------------------------------------------------------------------
/windows/runner/flutter_window.cpp:
--------------------------------------------------------------------------------
1 | #include "flutter_window.h"
2 |
3 | #include
4 |
5 | #include "flutter/generated_plugin_registrant.h"
6 |
7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project)
8 | : project_(project) {}
9 |
10 | FlutterWindow::~FlutterWindow() {}
11 |
12 | bool FlutterWindow::OnCreate() {
13 | if (!Win32Window::OnCreate()) {
14 | return false;
15 | }
16 |
17 | RECT frame = GetClientArea();
18 |
19 | // The size here must match the window dimensions to avoid unnecessary surface
20 | // creation / destruction in the startup path.
21 | flutter_controller_ = std::make_unique(
22 | frame.right - frame.left, frame.bottom - frame.top, project_);
23 | // Ensure that basic setup of the controller was successful.
24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) {
25 | return false;
26 | }
27 | RegisterPlugins(flutter_controller_->engine());
28 | SetChildContent(flutter_controller_->view()->GetNativeWindow());
29 | return true;
30 | }
31 |
32 | void FlutterWindow::OnDestroy() {
33 | if (flutter_controller_) {
34 | flutter_controller_ = nullptr;
35 | }
36 |
37 | Win32Window::OnDestroy();
38 | }
39 |
40 | LRESULT
41 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
42 | WPARAM const wparam,
43 | LPARAM const lparam) noexcept {
44 | // Give Flutter, including plugins, an opportunity to handle window messages.
45 | if (flutter_controller_) {
46 | std::optional result =
47 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
48 | lparam);
49 | if (result) {
50 | return *result;
51 | }
52 | }
53 |
54 | switch (message) {
55 | case WM_FONTCHANGE:
56 | flutter_controller_->engine()->ReloadSystemFonts();
57 | break;
58 | }
59 |
60 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
61 | }
62 |
--------------------------------------------------------------------------------
/windows/runner/flutter_window.h:
--------------------------------------------------------------------------------
1 | #ifndef RUNNER_FLUTTER_WINDOW_H_
2 | #define RUNNER_FLUTTER_WINDOW_H_
3 |
4 | #include
5 | #include
6 |
7 | #include
8 |
9 | #include "win32_window.h"
10 |
11 | // A window that does nothing but host a Flutter view.
12 | class FlutterWindow : public Win32Window {
13 | public:
14 | // Creates a new FlutterWindow hosting a Flutter view running |project|.
15 | explicit FlutterWindow(const flutter::DartProject& project);
16 | virtual ~FlutterWindow();
17 |
18 | protected:
19 | // Win32Window:
20 | bool OnCreate() override;
21 | void OnDestroy() override;
22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
23 | LPARAM const lparam) noexcept override;
24 |
25 | private:
26 | // The project to run.
27 | flutter::DartProject project_;
28 |
29 | // The Flutter instance hosted by this window.
30 | std::unique_ptr flutter_controller_;
31 | };
32 |
33 | #endif // RUNNER_FLUTTER_WINDOW_H_
34 |
--------------------------------------------------------------------------------
/windows/runner/main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include "flutter_window.h"
6 | #include "utils.h"
7 |
8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
9 | _In_ wchar_t *command_line, _In_ int show_command) {
10 | // Attach to console when present (e.g., 'flutter run') or create a
11 | // new console when running with a debugger.
12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
13 | CreateAndAttachConsole();
14 | }
15 |
16 | // Initialize COM, so that it is available for use in the library and/or
17 | // plugins.
18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
19 |
20 | flutter::DartProject project(L"data");
21 |
22 | std::vector command_line_arguments =
23 | GetCommandLineArguments();
24 |
25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
26 |
27 | FlutterWindow window(project);
28 | Win32Window::Point origin(10, 10);
29 | Win32Window::Size size(1280, 720);
30 | if (!window.CreateAndShow(L"flutter_puzzle", origin, size)) {
31 | return EXIT_FAILURE;
32 | }
33 | window.SetQuitOnClose(true);
34 |
35 | ::MSG msg;
36 | while (::GetMessage(&msg, nullptr, 0, 0)) {
37 | ::TranslateMessage(&msg);
38 | ::DispatchMessage(&msg);
39 | }
40 |
41 | ::CoUninitialize();
42 | return EXIT_SUCCESS;
43 | }
44 |
--------------------------------------------------------------------------------
/windows/runner/resource.h:
--------------------------------------------------------------------------------
1 | //{{NO_DEPENDENCIES}}
2 | // Microsoft Visual C++ generated include file.
3 | // Used by Runner.rc
4 | //
5 | #define IDI_APP_ICON 101
6 |
7 | // Next default values for new objects
8 | //
9 | #ifdef APSTUDIO_INVOKED
10 | #ifndef APSTUDIO_READONLY_SYMBOLS
11 | #define _APS_NEXT_RESOURCE_VALUE 102
12 | #define _APS_NEXT_COMMAND_VALUE 40001
13 | #define _APS_NEXT_CONTROL_VALUE 1001
14 | #define _APS_NEXT_SYMED_VALUE 101
15 | #endif
16 | #endif
17 |
--------------------------------------------------------------------------------
/windows/runner/resources/app_icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gottfired/flutter_puzzle/422ea096c9cc2a6e1ca652c07834b4a896bd23d8/windows/runner/resources/app_icon.ico
--------------------------------------------------------------------------------
/windows/runner/runner.exe.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PerMonitorV2
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/windows/runner/utils.cpp:
--------------------------------------------------------------------------------
1 | #include "utils.h"
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | #include
9 |
10 | void CreateAndAttachConsole() {
11 | if (::AllocConsole()) {
12 | FILE *unused;
13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
14 | _dup2(_fileno(stdout), 1);
15 | }
16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
17 | _dup2(_fileno(stdout), 2);
18 | }
19 | std::ios::sync_with_stdio();
20 | FlutterDesktopResyncOutputStreams();
21 | }
22 | }
23 |
24 | std::vector GetCommandLineArguments() {
25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
26 | int argc;
27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
28 | if (argv == nullptr) {
29 | return std::vector();
30 | }
31 |
32 | std::vector command_line_arguments;
33 |
34 | // Skip the first argument as it's the binary name.
35 | for (int i = 1; i < argc; i++) {
36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
37 | }
38 |
39 | ::LocalFree(argv);
40 |
41 | return command_line_arguments;
42 | }
43 |
44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) {
45 | if (utf16_string == nullptr) {
46 | return std::string();
47 | }
48 | int target_length = ::WideCharToMultiByte(
49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
50 | -1, nullptr, 0, nullptr, nullptr);
51 | std::string utf8_string;
52 | if (target_length == 0 || target_length > utf8_string.max_size()) {
53 | return utf8_string;
54 | }
55 | utf8_string.resize(target_length);
56 | int converted_length = ::WideCharToMultiByte(
57 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
58 | -1, utf8_string.data(),
59 | target_length, nullptr, nullptr);
60 | if (converted_length == 0) {
61 | return std::string();
62 | }
63 | return utf8_string;
64 | }
65 |
--------------------------------------------------------------------------------
/windows/runner/utils.h:
--------------------------------------------------------------------------------
1 | #ifndef RUNNER_UTILS_H_
2 | #define RUNNER_UTILS_H_
3 |
4 | #include
5 | #include
6 |
7 | // Creates a console for the process, and redirects stdout and stderr to
8 | // it for both the runner and the Flutter library.
9 | void CreateAndAttachConsole();
10 |
11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
12 | // encoded in UTF-8. Returns an empty std::string on failure.
13 | std::string Utf8FromUtf16(const wchar_t* utf16_string);
14 |
15 | // Gets the command line arguments passed in as a std::vector,
16 | // encoded in UTF-8. Returns an empty std::vector on failure.
17 | std::vector GetCommandLineArguments();
18 |
19 | #endif // RUNNER_UTILS_H_
20 |
--------------------------------------------------------------------------------
/windows/runner/win32_window.cpp:
--------------------------------------------------------------------------------
1 | #include "win32_window.h"
2 |
3 | #include
4 |
5 | #include "resource.h"
6 |
7 | namespace {
8 |
9 | constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
10 |
11 | // The number of Win32Window objects that currently exist.
12 | static int g_active_window_count = 0;
13 |
14 | using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
15 |
16 | // Scale helper to convert logical scaler values to physical using passed in
17 | // scale factor
18 | int Scale(int source, double scale_factor) {
19 | return static_cast(source * scale_factor);
20 | }
21 |
22 | // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
23 | // This API is only needed for PerMonitor V1 awareness mode.
24 | void EnableFullDpiSupportIfAvailable(HWND hwnd) {
25 | HMODULE user32_module = LoadLibraryA("User32.dll");
26 | if (!user32_module) {
27 | return;
28 | }
29 | auto enable_non_client_dpi_scaling =
30 | reinterpret_cast(
31 | GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
32 | if (enable_non_client_dpi_scaling != nullptr) {
33 | enable_non_client_dpi_scaling(hwnd);
34 | FreeLibrary(user32_module);
35 | }
36 | }
37 |
38 | } // namespace
39 |
40 | // Manages the Win32Window's window class registration.
41 | class WindowClassRegistrar {
42 | public:
43 | ~WindowClassRegistrar() = default;
44 |
45 | // Returns the singleton registar instance.
46 | static WindowClassRegistrar* GetInstance() {
47 | if (!instance_) {
48 | instance_ = new WindowClassRegistrar();
49 | }
50 | return instance_;
51 | }
52 |
53 | // Returns the name of the window class, registering the class if it hasn't
54 | // previously been registered.
55 | const wchar_t* GetWindowClass();
56 |
57 | // Unregisters the window class. Should only be called if there are no
58 | // instances of the window.
59 | void UnregisterWindowClass();
60 |
61 | private:
62 | WindowClassRegistrar() = default;
63 |
64 | static WindowClassRegistrar* instance_;
65 |
66 | bool class_registered_ = false;
67 | };
68 |
69 | WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
70 |
71 | const wchar_t* WindowClassRegistrar::GetWindowClass() {
72 | if (!class_registered_) {
73 | WNDCLASS window_class{};
74 | window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
75 | window_class.lpszClassName = kWindowClassName;
76 | window_class.style = CS_HREDRAW | CS_VREDRAW;
77 | window_class.cbClsExtra = 0;
78 | window_class.cbWndExtra = 0;
79 | window_class.hInstance = GetModuleHandle(nullptr);
80 | window_class.hIcon =
81 | LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
82 | window_class.hbrBackground = 0;
83 | window_class.lpszMenuName = nullptr;
84 | window_class.lpfnWndProc = Win32Window::WndProc;
85 | RegisterClass(&window_class);
86 | class_registered_ = true;
87 | }
88 | return kWindowClassName;
89 | }
90 |
91 | void WindowClassRegistrar::UnregisterWindowClass() {
92 | UnregisterClass(kWindowClassName, nullptr);
93 | class_registered_ = false;
94 | }
95 |
96 | Win32Window::Win32Window() {
97 | ++g_active_window_count;
98 | }
99 |
100 | Win32Window::~Win32Window() {
101 | --g_active_window_count;
102 | Destroy();
103 | }
104 |
105 | bool Win32Window::CreateAndShow(const std::wstring& title,
106 | const Point& origin,
107 | const Size& size) {
108 | Destroy();
109 |
110 | const wchar_t* window_class =
111 | WindowClassRegistrar::GetInstance()->GetWindowClass();
112 |
113 | const POINT target_point = {static_cast(origin.x),
114 | static_cast(origin.y)};
115 | HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
116 | UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
117 | double scale_factor = dpi / 96.0;
118 |
119 | HWND window = CreateWindow(
120 | window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
121 | Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
122 | Scale(size.width, scale_factor), Scale(size.height, scale_factor),
123 | nullptr, nullptr, GetModuleHandle(nullptr), this);
124 |
125 | if (!window) {
126 | return false;
127 | }
128 |
129 | return OnCreate();
130 | }
131 |
132 | // static
133 | LRESULT CALLBACK Win32Window::WndProc(HWND const window,
134 | UINT const message,
135 | WPARAM const wparam,
136 | LPARAM const lparam) noexcept {
137 | if (message == WM_NCCREATE) {
138 | auto window_struct = reinterpret_cast(lparam);
139 | SetWindowLongPtr(window, GWLP_USERDATA,
140 | reinterpret_cast(window_struct->lpCreateParams));
141 |
142 | auto that = static_cast(window_struct->lpCreateParams);
143 | EnableFullDpiSupportIfAvailable(window);
144 | that->window_handle_ = window;
145 | } else if (Win32Window* that = GetThisFromHandle(window)) {
146 | return that->MessageHandler(window, message, wparam, lparam);
147 | }
148 |
149 | return DefWindowProc(window, message, wparam, lparam);
150 | }
151 |
152 | LRESULT
153 | Win32Window::MessageHandler(HWND hwnd,
154 | UINT const message,
155 | WPARAM const wparam,
156 | LPARAM const lparam) noexcept {
157 | switch (message) {
158 | case WM_DESTROY:
159 | window_handle_ = nullptr;
160 | Destroy();
161 | if (quit_on_close_) {
162 | PostQuitMessage(0);
163 | }
164 | return 0;
165 |
166 | case WM_DPICHANGED: {
167 | auto newRectSize = reinterpret_cast(lparam);
168 | LONG newWidth = newRectSize->right - newRectSize->left;
169 | LONG newHeight = newRectSize->bottom - newRectSize->top;
170 |
171 | SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
172 | newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
173 |
174 | return 0;
175 | }
176 | case WM_SIZE: {
177 | RECT rect = GetClientArea();
178 | if (child_content_ != nullptr) {
179 | // Size and position the child window.
180 | MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
181 | rect.bottom - rect.top, TRUE);
182 | }
183 | return 0;
184 | }
185 |
186 | case WM_ACTIVATE:
187 | if (child_content_ != nullptr) {
188 | SetFocus(child_content_);
189 | }
190 | return 0;
191 | }
192 |
193 | return DefWindowProc(window_handle_, message, wparam, lparam);
194 | }
195 |
196 | void Win32Window::Destroy() {
197 | OnDestroy();
198 |
199 | if (window_handle_) {
200 | DestroyWindow(window_handle_);
201 | window_handle_ = nullptr;
202 | }
203 | if (g_active_window_count == 0) {
204 | WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
205 | }
206 | }
207 |
208 | Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
209 | return reinterpret_cast(
210 | GetWindowLongPtr(window, GWLP_USERDATA));
211 | }
212 |
213 | void Win32Window::SetChildContent(HWND content) {
214 | child_content_ = content;
215 | SetParent(content, window_handle_);
216 | RECT frame = GetClientArea();
217 |
218 | MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
219 | frame.bottom - frame.top, true);
220 |
221 | SetFocus(child_content_);
222 | }
223 |
224 | RECT Win32Window::GetClientArea() {
225 | RECT frame;
226 | GetClientRect(window_handle_, &frame);
227 | return frame;
228 | }
229 |
230 | HWND Win32Window::GetHandle() {
231 | return window_handle_;
232 | }
233 |
234 | void Win32Window::SetQuitOnClose(bool quit_on_close) {
235 | quit_on_close_ = quit_on_close;
236 | }
237 |
238 | bool Win32Window::OnCreate() {
239 | // No-op; provided for subclasses.
240 | return true;
241 | }
242 |
243 | void Win32Window::OnDestroy() {
244 | // No-op; provided for subclasses.
245 | }
246 |
--------------------------------------------------------------------------------
/windows/runner/win32_window.h:
--------------------------------------------------------------------------------
1 | #ifndef RUNNER_WIN32_WINDOW_H_
2 | #define RUNNER_WIN32_WINDOW_H_
3 |
4 | #include
5 |
6 | #include
7 | #include
8 | #include
9 |
10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be
11 | // inherited from by classes that wish to specialize with custom
12 | // rendering and input handling
13 | class Win32Window {
14 | public:
15 | struct Point {
16 | unsigned int x;
17 | unsigned int y;
18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {}
19 | };
20 |
21 | struct Size {
22 | unsigned int width;
23 | unsigned int height;
24 | Size(unsigned int width, unsigned int height)
25 | : width(width), height(height) {}
26 | };
27 |
28 | Win32Window();
29 | virtual ~Win32Window();
30 |
31 | // Creates and shows a win32 window with |title| and position and size using
32 | // |origin| and |size|. New windows are created on the default monitor. Window
33 | // sizes are specified to the OS in physical pixels, hence to ensure a
34 | // consistent size to will treat the width height passed in to this function
35 | // as logical pixels and scale to appropriate for the default monitor. Returns
36 | // true if the window was created successfully.
37 | bool CreateAndShow(const std::wstring& title,
38 | const Point& origin,
39 | const Size& size);
40 |
41 | // Release OS resources associated with window.
42 | void Destroy();
43 |
44 | // Inserts |content| into the window tree.
45 | void SetChildContent(HWND content);
46 |
47 | // Returns the backing Window handle to enable clients to set icon and other
48 | // window properties. Returns nullptr if the window has been destroyed.
49 | HWND GetHandle();
50 |
51 | // If true, closing this window will quit the application.
52 | void SetQuitOnClose(bool quit_on_close);
53 |
54 | // Return a RECT representing the bounds of the current client area.
55 | RECT GetClientArea();
56 |
57 | protected:
58 | // Processes and route salient window messages for mouse handling,
59 | // size change and DPI. Delegates handling of these to member overloads that
60 | // inheriting classes can handle.
61 | virtual LRESULT MessageHandler(HWND window,
62 | UINT const message,
63 | WPARAM const wparam,
64 | LPARAM const lparam) noexcept;
65 |
66 | // Called when CreateAndShow is called, allowing subclass window-related
67 | // setup. Subclasses should return false if setup fails.
68 | virtual bool OnCreate();
69 |
70 | // Called when Destroy is called.
71 | virtual void OnDestroy();
72 |
73 | private:
74 | friend class WindowClassRegistrar;
75 |
76 | // OS callback called by message pump. Handles the WM_NCCREATE message which
77 | // is passed when the non-client area is being created and enables automatic
78 | // non-client DPI scaling so that the non-client area automatically
79 | // responsponds to changes in DPI. All other messages are handled by
80 | // MessageHandler.
81 | static LRESULT CALLBACK WndProc(HWND const window,
82 | UINT const message,
83 | WPARAM const wparam,
84 | LPARAM const lparam) noexcept;
85 |
86 | // Retrieves a class instance pointer for |window|
87 | static Win32Window* GetThisFromHandle(HWND const window) noexcept;
88 |
89 | bool quit_on_close_ = false;
90 |
91 | // window handle for top level window.
92 | HWND window_handle_ = nullptr;
93 |
94 | // window handle for hosted content.
95 | HWND child_content_ = nullptr;
96 | };
97 |
98 | #endif // RUNNER_WIN32_WINDOW_H_
99 |
--------------------------------------------------------------------------------