├── .circleci └── config.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── boxy ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── example │ ├── .gitignore │ ├── .metadata │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── android │ │ ├── .gitignore │ │ ├── app │ │ │ ├── build.gradle │ │ │ └── src │ │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ │ ├── main │ │ │ │ ├── AndroidManifest.xml │ │ │ │ ├── kotlin │ │ │ │ │ └── com │ │ │ │ │ │ └── pxtst │ │ │ │ │ │ └── boxy │ │ │ │ │ │ └── gallery │ │ │ │ │ │ └── example │ │ │ │ │ │ └── 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 │ ├── ios │ │ ├── .gitignore │ │ ├── Flutter │ │ │ ├── AppFrameworkInfo.plist │ │ │ ├── Debug.xcconfig │ │ │ └── Release.xcconfig │ │ ├── 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-60x60@2x.png │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ │ └── LaunchImage.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ └── README.md │ │ │ ├── Base.lproj │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ │ ├── Info.plist │ │ │ └── Runner-Bridging-Header.h │ ├── lib │ │ ├── components │ │ │ └── palette.dart │ │ ├── main.dart │ │ └── pages │ │ │ ├── blog_tile.dart │ │ │ ├── boxy_row.dart │ │ │ ├── line_numbers.dart │ │ │ ├── product_tile.dart │ │ │ ├── product_tile_simple.dart │ │ │ ├── sliver_container.dart │ │ │ └── tree_view.dart │ ├── pubspec.yaml │ ├── web │ │ ├── favicon.png │ │ ├── icons │ │ │ ├── Icon-192.png │ │ │ ├── Icon-512.png │ │ │ ├── Icon-maskable-192.png │ │ │ └── Icon-maskable-512.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 ├── lib │ ├── boxy.dart │ ├── flex.dart │ ├── inflating_element.dart │ ├── padding.dart │ ├── redirect_pointer.dart │ ├── render_boxy.dart │ ├── scale.dart │ ├── slivers.dart │ ├── src │ │ ├── axis_utils.dart │ │ ├── boxy │ │ │ ├── box_child.dart │ │ │ ├── box_delegate.dart │ │ │ ├── custom_boxy.dart │ │ │ ├── custom_boxy_base.dart │ │ │ ├── inflating_element.dart │ │ │ ├── sliver_child.dart │ │ │ └── sliver_delegate.dart │ │ ├── boxy_flex.dart │ │ ├── overflow_padding.dart │ │ ├── redirect_pointer.dart │ │ ├── scale.dart │ │ ├── sliver_axis_utils.dart │ │ ├── sliver_card.dart │ │ ├── sliver_container.dart │ │ ├── sliver_offset.dart │ │ └── slotted_render_object_widget.dart │ └── utils.dart ├── pubspec.yaml └── test │ ├── boxy_intrinsics_test.dart │ ├── boxy_pointer_test.dart │ ├── boxy_sliver_test.dart │ ├── boxy_state_test.dart │ ├── build_scope_test.dart │ ├── common.dart │ ├── dry_layout_test.dart │ ├── flex_test.dart │ ├── layers_test.dart │ ├── parent_data_test.dart │ ├── redirect_pointer_test.dart │ ├── simple_column_test.dart │ ├── simple_inflation_test.dart │ ├── sliver_container_test.dart │ └── transform_test.dart └── website ├── .gitignore ├── README.md ├── SUMMARY.md ├── deploy.sh ├── docs ├── assets │ └── beeper.png ├── banner.png ├── custom-boxy │ ├── boxy-child.md │ ├── boxy-id.md │ ├── examples │ │ ├── evenly-sized-row.md │ │ ├── ftest_2ETeGIqwH8.png │ │ ├── ftest_4KetSQxXfe.png │ │ ├── ftest_7Je17IzRnr.png │ │ ├── ftest_GCKLEFXEnu.png │ │ ├── ftest_MyQB0wRzDZ.png │ │ ├── ftest_UHm3QStdY7.png │ │ ├── ftest_UTDKLOvcAU.png │ │ ├── ftest_X0YPu1QG3P.png │ │ ├── ftest_pqqiRVzZwz.png │ │ ├── image (1) (1) (1).png │ │ ├── product-tile.md │ │ ├── simplified_tree_view.png │ │ ├── square-layout.md │ │ └── tree-view.md │ ├── ftest_XBEjnnpsdS.png │ ├── ftest_fcR5Z2lEZD.png │ ├── ftest_frMkXTvID9.png │ ├── ftest_m3xCKjHuvM.png │ ├── hello-world.md │ ├── image (1) (1) (1) (1) (1) (1).png │ ├── image (1) (1) (1) (1) (1).png │ ├── image (1) (1) (1) (1).png │ ├── image (1).png │ ├── image (3).png │ ├── introduction-to-customboxy.md │ ├── layers.md │ ├── painting.md │ └── widget-inflation.md ├── helpers │ ├── cross-axis-alignment.md │ ├── dominant.md │ ├── ftest_IKQC577sJP.png │ ├── ftest_jInY0aelEY.png │ ├── ftest_nmZYWRSqsy (1).png │ ├── ftest_yiTCTxZ1s9.png │ ├── image (1) (1).png │ ├── image.png │ ├── sliver-card.md │ └── sliver-container.md ├── index.md ├── primer │ ├── box-constraints.md │ ├── image (2).png │ ├── interactive-example.md │ ├── introduction-to-layout.md │ ├── learn-more.md │ ├── the-many-trees.md │ └── trees.png └── stylesheets │ ├── boxy.css │ └── extra.css ├── mkdocs.yml └── overrides └── partials ├── comments.html └── tabs-item.html /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | aws-s3: circleci/aws-s3@3.0 5 | 6 | commands: 7 | install_flutter: 8 | steps: 9 | - run: 10 | name: "Install Flutter" 11 | command: |- 12 | touch $BASH_ENV 13 | curl -o- https://puro.dev/install.sh | PURO_VERSION="master" bash 14 | source $BASH_ENV 15 | puro flutter --version 16 | 17 | pub_get: 18 | steps: 19 | - run: 20 | name: "Pub Get" 21 | command: |- 22 | cd boxy 23 | flutter pub get 24 | 25 | format: 26 | steps: 27 | - run: 28 | name: "Format" 29 | command: |- 30 | dart format --set-exit-if-changed . 31 | 32 | analyze: 33 | steps: 34 | - run: 35 | name: "Analyze" 36 | command: |- 37 | dart analyze . 38 | 39 | test: 40 | steps: 41 | - run: 42 | name: "Test" 43 | command: |- 44 | cd boxy 45 | flutter test -r expanded 46 | 47 | test_all: 48 | steps: 49 | - checkout 50 | - pub_get 51 | - format 52 | - analyze 53 | - test 54 | 55 | jobs: 56 | linux_tests: 57 | docker: 58 | - image: cimg/base:stable 59 | steps: 60 | - install_flutter 61 | - test_all 62 | 63 | build_website: 64 | docker: 65 | - image: cimg/python:3.9 66 | steps: 67 | - checkout 68 | - add_ssh_keys: 69 | fingerprints: 70 | - "93:cb:27:b3:11:c7:99:17:7f:95:43:c9:89:fc:8c:be" 71 | - run: |- 72 | git clone https://github.com/PixelToast/mkdocs-material-insiders.git 73 | pip install ./mkdocs-material-insiders 74 | cd website 75 | export PYTHONPATH=`pwd` 76 | mkdocs build 77 | - aws-s3/sync: 78 | from: website/site/ 79 | to: s3://boxy-website 80 | - run: aws cloudfront create-invalidation --distribution-id $BOXY_CF_DISTRIBUTION --paths "/*" 81 | 82 | workflows: 83 | master: 84 | jobs: 85 | - build_website 86 | when: 87 | equal: [ "master", << pipeline.git.branch >> ] 88 | tests: 89 | jobs: 90 | - linux_tests 91 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IntelliJ related 2 | *.iml 3 | *.ipr 4 | *.iws 5 | .idea/ 6 | .vscode/ -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of conduct 2 | 3 | Boxy's code of conduct is an extension of [Flutter's code of conduct](https://github.com/flutter/flutter/blob/master/CODE_OF_CONDUCT.md). 4 | 5 | We expect contributors to act professionally and respectfully. 6 | 7 | Specifically: 8 | 9 | * Respect people, their identities, their culture, and their work. 10 | * Be kind. Be courteous. Be welcoming. 11 | * Listen. Consider and acknowledge people's points before responding. 12 | 13 | !["I try not to make fun of people for admitting they don't know things, because for each thing 'everyone knows' by the time they're adults, every day there are, on average, 10,000 people in the US hearing about it for the first time. If I make fun of people, I train them not to tell me when they have those moments. And I miss out on the fun." "Diet coke and mentos thing? What's that?" "Oh, man! We're going to the grocery store." "Why?" "You're one of today's lucky 10,000."](https://imgs.xkcd.com/comics/ten_thousand.png) 14 | 15 | Source: _[xkcd, May 2012](https://xkcd.com/1053/)_ -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Boxy 2 | 3 | Contributions are very welcome, feel free to reach out to ping#0001 on the [r/FlutterDev](https://discord.gg/rflutterdev) 4 | Discord server if you have any questions. 5 | 6 | ## Creating an issue 7 | 8 | The easiest way to contribute to Boxy is by creating an issue, we have the following tags: 9 | 10 | * Question 11 | * Bug 12 | * Documentation 13 | * Enhancement 14 | 15 | You are highly encouraged to create an issue before submitting a PR. 16 | 17 | ## PR Checklist 18 | 19 | This project has a relatively high standard for code quality, please refer to the [Flutter style guide](https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#philosophy). 20 | 21 | When submitting a PR, please make sure that: 22 | 23 | 1. Source files are formatted with dartfmt 24 | 2. All tests pass on the latest stable version of Flutter 25 | 3. There are no analyzer warnings 26 | 4. Bug fixes have appropriate tests (i.e. the test fails without the fix) 27 | 5. New public APIs are well documented 28 | 6. Breaking changes are well documented 29 | 7. Breaking changes follow semantic versioning 30 | 31 | Thank you for contributing! :) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | boxy/LICENSE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | boxy/README.md -------------------------------------------------------------------------------- /boxy/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | pubspec.lock 32 | build/ 33 | coverage/ 34 | 35 | # Android related 36 | **/android/**/gradle-wrapper.jar 37 | **/android/.gradle 38 | **/android/captures/ 39 | **/android/gradlew 40 | **/android/gradlew.bat 41 | **/android/local.properties 42 | **/android/**/GeneratedPluginRegistrant.java 43 | 44 | # iOS/XCode related 45 | **/ios/**/*.mode1v3 46 | **/ios/**/*.mode2v3 47 | **/ios/**/*.moved-aside 48 | **/ios/**/*.pbxuser 49 | **/ios/**/*.perspectivev3 50 | **/ios/**/*sync/ 51 | **/ios/**/.sconsign.dblite 52 | **/ios/**/.tags* 53 | **/ios/**/.vagrant/ 54 | **/ios/**/DerivedData/ 55 | **/ios/**/Icon? 56 | **/ios/**/Pods/ 57 | **/ios/**/.symlinks/ 58 | **/ios/**/profile 59 | **/ios/**/xcuserdata 60 | **/ios/.generated/ 61 | **/ios/Flutter/App.framework 62 | **/ios/Flutter/Flutter.framework 63 | **/ios/Flutter/Flutter.podspec 64 | **/ios/Flutter/Generated.xcconfig 65 | **/ios/Flutter/app.flx 66 | **/ios/Flutter/app.zip 67 | **/ios/Flutter/flutter_assets/ 68 | **/ios/Flutter/flutter_export_environment.sh 69 | **/ios/ServiceDefinitions.json 70 | **/ios/Runner/GeneratedPluginRegistrant.* 71 | 72 | # Exceptions to above rules. 73 | !**/ios/**/default.mode1v3 74 | !**/ios/**/default.mode2v3 75 | !**/ios/**/default.pbxuser 76 | !**/ios/**/default.perspectivev3 77 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 78 | 79 | *.lock 80 | -------------------------------------------------------------------------------- /boxy/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 67826bdce54505760fe83b7ead70bdb5af6fe9f2 8 | channel: dev 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /boxy/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [2.2.1] 2 | * Fixed small bug with `BoxyChild.offset`, thanks to Albert221 for the PR! 3 | 4 | ## [2.2.0] 5 | * Added `BoxyDelegate.getChildOrNull`, thanks to jtarkowski27 for the PR! 6 | 7 | ## [2.1.3] 8 | * Fixed visitChildrenForSemantics being called for ignored children 9 | 10 | ## [2.1.2] 11 | * Fixed `RedirectPointer` not being exported, thanks to mattermoran for the PR! 12 | 13 | ## [2.1.1] 14 | * Added the `Scale` widget which scales its child by a given factor while preserving relative size unlike `Transform` 15 | * Added the `RedirectPointer` widget which allows you to hit test a child widget even if it overpaints its parent 16 | 17 | ## [2.1.0] 18 | * Support for Flutter 3.13 19 | 20 | ## [2.0.9] 21 | * Skipped calls to buildScope as a minor performance improvement 22 | 23 | ## [2.0.8] 24 | * Fixed bug with retaining `Layer`s 25 | 26 | ## [2.0.7] 27 | * Fixed example project 28 | * Added `BoxyChild.positionRect` 29 | 30 | ## [2.0.6+2] 31 | * Added `BoxyDelegate.distanceToBaseline` 32 | * Added `BoxyDelegate.onPointerEvent` 33 | * Fixed constraints bug in `BoxyChild.layoutRect` 34 | 35 | ## [2.0.6+1] 36 | * Added `BoxyDelegate.renderSize` 37 | * Fixed slot bug in `SliverContainer` 38 | * Fixed overflow bug in `BoxyFlex` 39 | 40 | ## [2.0.6] 41 | * Added `BoxyLayerContext.imageFilter` 42 | 43 | ## [2.0.5+1] 44 | * Fixed small bug in BoxyChild.paint 45 | 46 | ## [2.0.5] 47 | * Implemented `BoxyChild.context` 48 | 49 | ## [2.0.4] 50 | * Fixed an issue with `BoxyId.data` not being applied before first layout 51 | 52 | ## [2.0.3] 53 | * Fixed an issue with accessing `CustomBoxy` children during intrinsic layout 54 | 55 | ## [2.0.2] 56 | * Added the `BoxyFlexible.align` constructor 57 | 58 | ## [2.0.1] 59 | * Added `BoxyFlexIntrinsicsBehavior` 60 | * Fixed an issue with the intrinsic sizing of `BoxyFlex` 61 | 62 | ## [2.0.0] 63 | * Added sliver support to `CustomBoxy` 64 | * Added `BoxyId`, a replacement of `LayoutId` 65 | * Added the `CustomBoxy.box` and `CustomBoxy.sliver` constructors 66 | * Added the `BoxBoxyDelegate` and `SliverBoxyDelegate` boxy delegates 67 | * Added `SliverBoxyChild` for managing sliver children 68 | * Added the `SliverOffset` and `SliverSize` wrappers 69 | * Added extensions for `SliverConstraints` and `RenderSliver` 70 | * Separated the internal logic of `CustomBoxy` into the `inflating_element` and `render_boxy` libraries 71 | * Bug fixes 72 | 73 | ## [1.3.0] 74 | 75 | * Migrate to null safety 76 | * Added `BoxyChild.parentData` 77 | * Added `LayerKey` and `BoxyLayerContext` 78 | * Added dry layouts 79 | * Added the `Dominant.flexible` and `Dominant.expanded` constructors 80 | * Bug fixes 81 | 82 | ## [1.2.0+1] 83 | 84 | * Transition deprecated `RenderObjectElement` methods (flutter/#63269) 85 | 86 | ## [1.2.0] 87 | 88 | * Bug fixes 89 | * `BoxyChild.layout` no longer sets a default hasSize 90 | 91 | ## [1.1.1] 92 | 93 | * Bug fixes 94 | 95 | ## [1.1.0] 96 | 97 | * Added `SliverContainer` / `SliverCard` 98 | * Added more axis/direction utilities 99 | * Added OverflowPadding 100 | * Bug fixes 101 | 102 | ## [1.0.1] 103 | 104 | * Fixed Flutter SDK version constraints 105 | 106 | ## [1.0.0] - Initial release 107 | -------------------------------------------------------------------------------- /boxy/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Andre Lipke 2 | 3 | Copyright 2014 The Flutter Authors. All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following 12 | disclaimer in the documentation and/or other materials provided 13 | with the distribution. 14 | * Neither the name of the copyright holder nor the names of its 15 | contributors may be used to endorse or promote products derived 16 | from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /boxy/README.md: -------------------------------------------------------------------------------- 1 | ![boxy, Layout made simple](https://i.tst.sh/zncIM.png) 2 | 3 | ## About 4 | 5 | Boxy is a Flutter package created to overcome the limitations of built-in layout widgets, it provides utilities for flex, custom multi-child layouts, dynamic widget inflation, slivers, and more! 6 | 7 | The package is ready for production use, it has excellent documentation, test coverage, and passes strict analysis. 8 | 9 | Check out the new wiki! https://boxy.wiki 10 | 11 | ## Flex layouts 12 | 13 | A common design problem is when you need one or more children of a `Row` or `Column` to have the same cross-axis size 14 | as another child in the list, with boxy this can be achieved trivially using `BoxyRow`, `BoxyColumn` and `Dominant`. 15 | 16 | ![Using BoxyRow. A sidebar matches the height of a dynamically sized container](https://i.tst.sh/WDmbR.png) 17 | 18 | ![Using BoxyColumn. An underbar matches the width of a dynamically sized container](https://i.tst.sh/FdoiA.png) 19 | 20 | ![Using BoxyColumn and BoxyFlexible.align; The top child has a custom cross axis alignment from the others](https://i.tst.sh/Bn42V.png) 21 | 22 | See the documentation of [BoxyRow](https://pub.dev/documentation/boxy/latest/flex/BoxyRow-class.html) and 23 | [BoxyColumn](https://pub.dev/documentation/boxy/latest/flex/BoxyColumn-class.html) for more information. 24 | 25 | ## Custom layouts 26 | 27 | One of the pains of implementing custom layouts is learning the `RenderObject` model and how verbose it is, to make this 28 | process easier we provide an extremely simple container `CustomBoxy` that delegates layout, paint, and hit testing. 29 | 30 | ![1. Declare widget using CustomBoxy 2. Implement delegate. Dynamic header and content in a column with an avatar pinned to the center of both](https://i.tst.sh/e0M7b.png) 31 | 32 | The most powerful feature of `CustomBoxy` is the ability to inflate arbitrary widgets at layout time, this means widgets 33 | can depend on the size of others, something previously impossible without hacky workarounds. 34 | 35 | ![Lazy-loading children with BoxyDelegate.inflate to match the width of a container](https://i.tst.sh/sYQHo.png) 36 | 37 | See the documentation of [CustomBoxy](https://pub.dev/documentation/boxy/latest/boxy/CustomBoxy-class.html) and 38 | [BoxyDelegate](https://pub.dev/documentation/boxy/latest/boxy/BoxyDelegate-class.html) for more information. 39 | 40 | ## Slivers 41 | 42 | Ever want to give SliverList a box decoration? The [sliver](https://pub.dev/documentation/boxy/latest/sliver) library 43 | provides [SliverContainer](https://pub.dev/documentation/boxy/latest/slivers/SliverContainer-class.html) which allows 44 | you to use box widgets as the foreground or background of a sliver. 45 | 46 | This library also provides [SliverCard](https://pub.dev/documentation/boxy/latest/slivers/SliverCard-class.html), a 47 | [SliverContainer](https://pub.dev/documentation/boxy/latest/slivers/SliverContainer-class.html) that looks like a card. 48 | 49 | ![Adding a custom card-like background to a SliverList, while still building lazily](https://i.tst.sh/iiyrk.png) 50 | 51 | Also check out: 52 | * [SliverPadding](https://api.flutter.dev/flutter/widgets/SliverPadding-class.html), an underrated built-in widget 53 | * [sliver_tools](https://pub.dev/packages/sliver_tools), a cool package that does similar things 54 | 55 | ## Miscellaneous 56 | 57 | The [utils](https://pub.dev/documentation/boxy/latest/utils/utils-library.html) library provides extensions with dozens 58 | of axis-dependant methods on `BoxConstraints`, `Size`, `Offset`, and more. These extensions make writing directional 59 | layouts significantly less cumbersome. 60 | 61 | The [OverflowPadding](https://pub.dev/documentation/boxy/latest/padding/OverflowPadding-class.html) widget is similar to 62 | `Padding` but allows the child to overflow when given negative insets. -------------------------------------------------------------------------------- /boxy/example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Symbolication related 37 | app.*.symbols 38 | 39 | # Exceptions to above rules. 40 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 41 | -------------------------------------------------------------------------------- /boxy/example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled. 5 | 6 | version: 7 | revision: 099b3f4bf1581796fac3848d3381dbf2f29edbea 8 | channel: beta 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 099b3f4bf1581796fac3848d3381dbf2f29edbea 17 | base_revision: 099b3f4bf1581796fac3848d3381dbf2f29edbea 18 | - platform: windows 19 | create_revision: 099b3f4bf1581796fac3848d3381dbf2f29edbea 20 | base_revision: 099b3f4bf1581796fac3848d3381dbf2f29edbea 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 | -------------------------------------------------------------------------------- /boxy/example/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Andre Lipke 2 | 3 | Copyright 2014 The Flutter Authors. All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following 12 | disclaimer in the documentation and/or other materials provided 13 | with the distribution. 14 | * Neither the name of the copyright holder nor the names of its 15 | contributors may be used to endorse or promote products derived 16 | from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /boxy/example/README.md: -------------------------------------------------------------------------------- 1 | # Boxy Gallery 2 | 3 | A set of examples for the boxy package, so far it contains the following pages: 4 | 1. Tree view: Complex layout that displays a tree of widgets with centered branches. 5 | 2. Product tile: Layout that positions a widget so that it floats on the border between two other widgets. -------------------------------------------------------------------------------- /boxy/example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: ../analysis_options.yaml 2 | 3 | analyzer: 4 | errors: 5 | public_member_api_docs: ignore 6 | use_key_in_widget_constructors: ignore 7 | -------------------------------------------------------------------------------- /boxy/example/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 | -------------------------------------------------------------------------------- /boxy/example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion flutter.compileSdkVersion 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.pxtst.boxy.gallery.example" 47 | minSdkVersion flutter.minSdkVersion 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 | -------------------------------------------------------------------------------- /boxy/example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /boxy/example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 15 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /boxy/example/android/app/src/main/kotlin/com/pxtst/boxy/gallery/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.pxtst.boxy.gallery.example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /boxy/example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /boxy/example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /boxy/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/boxy/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /boxy/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/boxy/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /boxy/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/boxy/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /boxy/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/boxy/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /boxy/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/boxy/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /boxy/example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /boxy/example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /boxy/example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /boxy/example/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 | -------------------------------------------------------------------------------- /boxy/example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /boxy/example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip 7 | -------------------------------------------------------------------------------- /boxy/example/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 | -------------------------------------------------------------------------------- /boxy/example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /boxy/example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /boxy/example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /boxy/example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /boxy/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /boxy/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /boxy/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /boxy/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /boxy/example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /boxy/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /boxy/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /boxy/example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /boxy/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /boxy/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/boxy/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /boxy/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/boxy/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /boxy/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/boxy/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /boxy/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/boxy/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /boxy/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/boxy/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /boxy/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/boxy/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /boxy/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/boxy/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /boxy/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/boxy/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /boxy/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/boxy/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /boxy/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/boxy/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /boxy/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/boxy/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /boxy/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/boxy/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /boxy/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/boxy/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /boxy/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/boxy/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /boxy/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/boxy/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /boxy/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /boxy/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/boxy/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /boxy/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/boxy/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /boxy/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/boxy/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /boxy/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /boxy/example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /boxy/example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /boxy/example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | gallery 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /boxy/example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /boxy/example/lib/components/palette.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Palette { 4 | final Color background; 5 | final Color foreground; 6 | final Color accent; 7 | final Color divider; 8 | final Color primary; 9 | final Color secondary; 10 | final Color highlight; 11 | 12 | const Palette({ 13 | required this.background, 14 | required this.foreground, 15 | required this.accent, 16 | required this.divider, 17 | required this.primary, 18 | required this.secondary, 19 | required this.highlight, 20 | }); 21 | } 22 | 23 | Palette get palette => const Palette( 24 | background: Color(0xFF3A2E39), 25 | foreground: Color(0xFFF4D8CD), 26 | accent: Color(0xFFF15152), 27 | divider: Color(0xFF5F4B5E), 28 | primary: Color(0xFF5F4B5E), 29 | secondary: Color(0xFF678D58), 30 | highlight: Color(0xFFEDB183), 31 | ); 32 | -------------------------------------------------------------------------------- /boxy/example/lib/pages/boxy_row.dart: -------------------------------------------------------------------------------- 1 | import 'package:boxy/flex.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import '../main.dart'; 5 | 6 | class BoxyRowPage extends StatefulWidget { 7 | @override 8 | State createState() => BoxyRowPageState(); 9 | } 10 | 11 | final rainbow = [ 12 | Colors.red, 13 | Colors.deepPurple, 14 | Colors.lightBlue, 15 | Colors.green, 16 | Colors.amber, 17 | ]; 18 | 19 | const shades = [400, 500, 600, 700, 800, 900]; 20 | 21 | Color lerpGradient(List colors, List stops, double t) { 22 | for (var s = 0; s < stops.length - 1; s++) { 23 | final leftStop = stops[s], rightStop = stops[s + 1]; 24 | final leftColor = colors[s], rightColor = colors[s + 1]; 25 | if (t <= leftStop) { 26 | return leftColor; 27 | } else if (t < rightStop) { 28 | final sectionT = (t - leftStop) / (rightStop - leftStop); 29 | return Color.lerp(leftColor, rightColor, sectionT)!; 30 | } 31 | } 32 | return colors.last; 33 | } 34 | 35 | Color getRainbowColor(double delta) { 36 | return lerpGradient( 37 | rainbow, 38 | [ 39 | for (int i = 0; i < rainbow.length; i++) i / (rainbow.length - 1), 40 | ], 41 | delta, 42 | ); 43 | } 44 | 45 | class BoxyRowPageState extends State { 46 | @override 47 | Widget build(BuildContext context) { 48 | return Scaffold( 49 | appBar: const GalleryAppBar( 50 | ['Boxy Gallery', 'BoxyRow'], 51 | source: 52 | 'https://github.com/pingbird/boxy/blob/master/boxy/example/lib/pages/boxy_row.dart', 53 | ), 54 | body: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [ 55 | Separator(), 56 | Expanded( 57 | child: Align( 58 | widthFactor: 1, 59 | heightFactor: 1, 60 | child: LabelBox( 61 | label: 'BoxyRow', 62 | child: BoxyRow( 63 | mainAxisSize: MainAxisSize.min, 64 | children: const [ 65 | ChildCard(text: 'Child 1', color: Colors.red), 66 | Dominant( 67 | child: LabelBox( 68 | label: 'Dominant', 69 | child: LabelBox( 70 | label: 'Column', 71 | child: Column( 72 | mainAxisSize: MainAxisSize.min, 73 | children: [ 74 | ChildCard( 75 | text: 'Child 2', color: Colors.lightGreen), 76 | ChildCard(text: 'Child 3', color: Colors.blue), 77 | ], 78 | ), 79 | ), 80 | ), 81 | ), 82 | ], 83 | ), 84 | ), 85 | ), 86 | ), 87 | Separator(), 88 | ]), 89 | ); 90 | } 91 | } 92 | 93 | class ChildCard extends StatefulWidget { 94 | final String text; 95 | final Color color; 96 | 97 | const ChildCard({ 98 | required this.text, 99 | required this.color, 100 | }); 101 | 102 | @override 103 | ChildCardState createState() => ChildCardState(); 104 | } 105 | 106 | class ChildCardState extends State 107 | with SingleTickerProviderStateMixin { 108 | int state = 0; 109 | 110 | late AnimationController anim; 111 | 112 | @override 113 | void initState() { 114 | super.initState(); 115 | anim = AnimationController( 116 | duration: const Duration(milliseconds: 300), 117 | vsync: this, 118 | upperBound: 2); 119 | anim.addListener(() => setState(() {})); 120 | } 121 | 122 | @override 123 | void dispose() { 124 | super.dispose(); 125 | anim.dispose(); 126 | } 127 | 128 | @override 129 | Card build(context) { 130 | return Card( 131 | color: widget.color, 132 | child: InkWell( 133 | onTap: () => setState(() { 134 | state = (state + 1) % 2; 135 | anim.animateTo(state.toDouble(), curve: Curves.ease); 136 | }), 137 | child: SizedBox( 138 | width: 80 + anim.value * 45, 139 | height: 80 + anim.value * 45, 140 | child: Center(child: Text(widget.text)), 141 | ), 142 | ), 143 | ); 144 | } 145 | } 146 | 147 | class LabelBox extends StatelessWidget { 148 | final String label; 149 | final Widget child; 150 | 151 | const LabelBox({required this.label, required this.child}); 152 | 153 | @override 154 | Widget build(BuildContext context) { 155 | return Stack( 156 | children: [ 157 | Container( 158 | margin: const EdgeInsets.all(7), 159 | padding: const EdgeInsets.all(7), 160 | decoration: BoxDecoration( 161 | borderRadius: BorderRadius.circular(8), 162 | border: Border.all(color: Colors.white.withOpacity(0.2)), 163 | ), 164 | child: child, 165 | ), 166 | Positioned( 167 | left: 0, 168 | right: 0, 169 | top: 0, 170 | child: Align( 171 | child: Container( 172 | padding: const EdgeInsets.symmetric(horizontal: 8), 173 | child: Text(label), 174 | ), 175 | ), 176 | ), 177 | ], 178 | ); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /boxy/example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: boxy_gallery 2 | description: Examples for the boxy package. 3 | version: 1.0.0 4 | homepage: https://github.com/pingbird/boxy 5 | 6 | environment: 7 | sdk: '>=2.18.0 <3.0.0' 8 | 9 | dependencies: 10 | boxy: ^2.0.6+2 11 | cupertino_icons: ^1.0.2 12 | flutter: 13 | sdk: flutter 14 | material_design_icons_flutter: ^5.0.5955-rc.1 15 | rxdart: ^0.26.0 16 | tuple: ^2.0.0 17 | url_launcher: ^6.0.2 18 | 19 | dev_dependencies: 20 | flutter_test: 21 | sdk: flutter 22 | 23 | flutter: 24 | uses-material-design: true 25 | 26 | dependency_overrides: 27 | boxy: 28 | path: ../ 29 | -------------------------------------------------------------------------------- /boxy/example/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/boxy/example/web/favicon.png -------------------------------------------------------------------------------- /boxy/example/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/boxy/example/web/icons/Icon-192.png -------------------------------------------------------------------------------- /boxy/example/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/boxy/example/web/icons/Icon-512.png -------------------------------------------------------------------------------- /boxy/example/web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/boxy/example/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /boxy/example/web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/boxy/example/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /boxy/example/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | gallery 18 | 19 | 20 | 21 | 24 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /boxy/example/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gallery", 3 | "short_name": "gallery", 4 | "start_url": ".", 5 | "display": "minimal-ui", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 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 | } 24 | -------------------------------------------------------------------------------- /boxy/example/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 | -------------------------------------------------------------------------------- /boxy/example/windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project-level configuration. 2 | cmake_minimum_required(VERSION 3.14) 3 | project(example 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 "example") 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 | -------------------------------------------------------------------------------- /boxy/example/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 | -------------------------------------------------------------------------------- /boxy/example/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 | 11 | void RegisterPlugins(flutter::PluginRegistry* registry) { 12 | UrlLauncherWindowsRegisterWithRegistrar( 13 | registry->GetRegistrarForPlugin("UrlLauncherWindows")); 14 | } 15 | -------------------------------------------------------------------------------- /boxy/example/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 | -------------------------------------------------------------------------------- /boxy/example/windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | url_launcher_windows 7 | ) 8 | 9 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 10 | ) 11 | 12 | set(PLUGIN_BUNDLED_LIBRARIES) 13 | 14 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 15 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 16 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 18 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 19 | endforeach(plugin) 20 | 21 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 22 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 23 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 24 | endforeach(ffi_plugin) 25 | -------------------------------------------------------------------------------- /boxy/example/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 | # Add preprocessor definitions for the build version. 24 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") 25 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") 26 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") 27 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") 28 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") 29 | 30 | # Disable Windows macros that collide with C++ standard library functions. 31 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 32 | 33 | # Add dependency libraries and include directories. Add any application-specific 34 | # dependencies here. 35 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 36 | target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") 37 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 38 | 39 | # Run the Flutter tool portions of the build. This must not be removed. 40 | add_dependencies(${BINARY_NAME} flutter_assemble) 41 | -------------------------------------------------------------------------------- /boxy/example/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 | #if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) 64 | #define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD 65 | #else 66 | #define VERSION_AS_NUMBER 1,0,0,0 67 | #endif 68 | 69 | #if defined(FLUTTER_VERSION) 70 | #define VERSION_AS_STRING FLUTTER_VERSION 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.pxtst.boxy.gallery" "\0" 93 | VALUE "FileDescription", "example" "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "example" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2023 com.pxtst.boxy.gallery. All rights reserved." "\0" 97 | VALUE "OriginalFilename", "example.exe" "\0" 98 | VALUE "ProductName", "example" "\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 | -------------------------------------------------------------------------------- /boxy/example/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 | 30 | flutter_controller_->engine()->SetNextFrameCallback([&]() { 31 | this->Show(); 32 | }); 33 | 34 | return true; 35 | } 36 | 37 | void FlutterWindow::OnDestroy() { 38 | if (flutter_controller_) { 39 | flutter_controller_ = nullptr; 40 | } 41 | 42 | Win32Window::OnDestroy(); 43 | } 44 | 45 | LRESULT 46 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 47 | WPARAM const wparam, 48 | LPARAM const lparam) noexcept { 49 | // Give Flutter, including plugins, an opportunity to handle window messages. 50 | if (flutter_controller_) { 51 | std::optional result = 52 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 53 | lparam); 54 | if (result) { 55 | return *result; 56 | } 57 | } 58 | 59 | switch (message) { 60 | case WM_FONTCHANGE: 61 | flutter_controller_->engine()->ReloadSystemFonts(); 62 | break; 63 | } 64 | 65 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 66 | } 67 | -------------------------------------------------------------------------------- /boxy/example/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 | -------------------------------------------------------------------------------- /boxy/example/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.Create(L"example", 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 | -------------------------------------------------------------------------------- /boxy/example/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 | -------------------------------------------------------------------------------- /boxy/example/windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/boxy/example/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /boxy/example/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 | -------------------------------------------------------------------------------- /boxy/example/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 | -------------------------------------------------------------------------------- /boxy/example/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 | -------------------------------------------------------------------------------- /boxy/example/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 a win32 window with |title| that is positioned and sized 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 this function will scale the inputted width and height as 35 | // as appropriate for the default monitor. The window is invisible until 36 | // |Show| is called. Returns true if the window was created successfully. 37 | bool Create(const std::wstring& title, const Point& origin, const Size& size); 38 | 39 | // Show the current window. Returns true if the window was successfully shown. 40 | bool Show(); 41 | 42 | // Release OS resources associated with window. 43 | void Destroy(); 44 | 45 | // Inserts |content| into the window tree. 46 | void SetChildContent(HWND content); 47 | 48 | // Returns the backing Window handle to enable clients to set icon and other 49 | // window properties. Returns nullptr if the window has been destroyed. 50 | HWND GetHandle(); 51 | 52 | // If true, closing this window will quit the application. 53 | void SetQuitOnClose(bool quit_on_close); 54 | 55 | // Return a RECT representing the bounds of the current client area. 56 | RECT GetClientArea(); 57 | 58 | protected: 59 | // Processes and route salient window messages for mouse handling, 60 | // size change and DPI. Delegates handling of these to member overloads that 61 | // inheriting classes can handle. 62 | virtual LRESULT MessageHandler(HWND window, 63 | UINT const message, 64 | WPARAM const wparam, 65 | LPARAM const lparam) noexcept; 66 | 67 | // Called when CreateAndShow is called, allowing subclass window-related 68 | // setup. Subclasses should return false if setup fails. 69 | virtual bool OnCreate(); 70 | 71 | // Called when Destroy is called. 72 | virtual void OnDestroy(); 73 | 74 | private: 75 | friend class WindowClassRegistrar; 76 | 77 | // OS callback called by message pump. Handles the WM_NCCREATE message which 78 | // is passed when the non-client area is being created and enables automatic 79 | // non-client DPI scaling so that the non-client area automatically 80 | // responsponds to changes in DPI. All other messages are handled by 81 | // MessageHandler. 82 | static LRESULT CALLBACK WndProc(HWND const window, 83 | UINT const message, 84 | WPARAM const wparam, 85 | LPARAM const lparam) noexcept; 86 | 87 | // Retrieves a class instance pointer for |window| 88 | static Win32Window* GetThisFromHandle(HWND const window) noexcept; 89 | 90 | // Update the window frame's theme to match the system theme. 91 | static void UpdateTheme(HWND const window); 92 | 93 | bool quit_on_close_ = false; 94 | 95 | // window handle for top level window. 96 | HWND window_handle_ = nullptr; 97 | 98 | // window handle for hosted content. 99 | HWND child_content_ = nullptr; 100 | }; 101 | 102 | #endif // RUNNER_WIN32_WINDOW_H_ 103 | -------------------------------------------------------------------------------- /boxy/lib/boxy.dart: -------------------------------------------------------------------------------- 1 | /// This library contains [CustomBoxy], a widget that uses a delegate to control 2 | /// the layout of multiple children. 3 | library boxy; 4 | 5 | export 'src/boxy/box_child.dart' show BoxyChild; 6 | export 'src/boxy/box_delegate.dart' show BoxBoxyDelegate, BoxyDelegate; 7 | export 'src/boxy/custom_boxy.dart'; 8 | export 'src/boxy/custom_boxy_base.dart' 9 | show BaseBoxyChild, BoxyDelegatePhase, BoxyId, BoxyLayerContext, LayerKey; 10 | export 'src/boxy/sliver_child.dart' show SliverBoxyChild; 11 | export 'src/boxy/sliver_delegate.dart' show SliverBoxyDelegate; 12 | export 'src/sliver_offset.dart'; 13 | -------------------------------------------------------------------------------- /boxy/lib/flex.dart: -------------------------------------------------------------------------------- 1 | /// This library contains the [BoxyFlex], [BoxyColumn] and [BoxyRow] widgets. 2 | /// 3 | /// These layouts are useful for when you need the cross-axis size of one child 4 | /// in a [Row] or [Column] to influence the cross-axis size of others. 5 | library flex; 6 | 7 | export 'src/boxy_flex.dart'; 8 | -------------------------------------------------------------------------------- /boxy/lib/inflating_element.dart: -------------------------------------------------------------------------------- 1 | /// This library contains the internal logic of [CustomBoxy], useful if you want 2 | /// to implement a custom [RenderObject] that inflates arbitrary widgets at 3 | /// layout time. 4 | /// 5 | /// [InflatingElement] works in a similar fashion to [LayoutBuilder], calling 6 | /// [BuildOwner.buildScope] to create a build scope and 7 | /// [RenderObject.invokeLayoutCallback] to allow tree mutations. 8 | library inflating_element; 9 | 10 | export 'src/boxy/inflating_element.dart'; 11 | -------------------------------------------------------------------------------- /boxy/lib/padding.dart: -------------------------------------------------------------------------------- 1 | export 'src/overflow_padding.dart'; 2 | -------------------------------------------------------------------------------- /boxy/lib/redirect_pointer.dart: -------------------------------------------------------------------------------- 1 | export 'src/redirect_pointer.dart'; 2 | -------------------------------------------------------------------------------- /boxy/lib/render_boxy.dart: -------------------------------------------------------------------------------- 1 | /// This library contains the guts of [CustomBoxy], useful if you want to 2 | /// integrate a custom render protocol. 3 | /// 4 | /// In most cases digging this deep is not necessary, consider using the 5 | /// [CustomBoxy] widget directly. 6 | library render_boxy; 7 | 8 | export 'src/boxy/box_child.dart' hide BoxyChild; 9 | export 'src/boxy/box_delegate.dart' hide BoxBoxyDelegate, BoxyDelegate; 10 | export 'src/boxy/custom_boxy_base.dart' 11 | hide BaseBoxyChild, BoxyDelegatePhase, BoxyId, BoxyLayerContext, LayerKey; 12 | export 'src/boxy/sliver_child.dart' hide SliverBoxyChild; 13 | export 'src/boxy/sliver_delegate.dart' hide SliverBoxyDelegate; 14 | -------------------------------------------------------------------------------- /boxy/lib/scale.dart: -------------------------------------------------------------------------------- 1 | export 'src/scale.dart'; 2 | -------------------------------------------------------------------------------- /boxy/lib/slivers.dart: -------------------------------------------------------------------------------- 1 | /// This library provides [SliverCard] and [SliverContainer], useful for giving 2 | /// box decorations to lazily built slivers. 3 | library slivers; 4 | 5 | export 'src/sliver_card.dart'; 6 | export 'src/sliver_container.dart'; 7 | -------------------------------------------------------------------------------- /boxy/lib/src/redirect_pointer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/rendering.dart'; 3 | 4 | /// Redirects pointer events to widgets anywhere else in the tree. 5 | /// 6 | /// This is useful for widgets that overflow their parent such as from a 7 | /// [Transform] and would otherwise not receive pointer events. 8 | /// 9 | /// To use this widget, give the child you would like to receive pointer events 10 | /// a [GlobalKey] and pass it to the [above] or [below] parameter. 11 | /// 12 | /// Note that the RedirectPointer widget needs to encompass the entire widget 13 | /// that should receive pointer events, we recommend wrapping the body of the 14 | /// [Scaffold] so that it can receive pointer events for the whole screen. 15 | /// 16 | /// You may also want to wrap the targets in an [IgnorePointer] so they aren't 17 | /// hit tested more than once. 18 | /// 19 | /// Hit testing is performed in the following order: 20 | /// 1. The [above] widgets in reverse. 21 | /// 2. The child. 22 | /// 3. The [below] widgets in reverse. 23 | class RedirectPointer extends SingleChildRenderObjectWidget { 24 | const RedirectPointer({ 25 | super.key, 26 | super.child, 27 | this.above = const [], 28 | this.below = const [], 29 | }); 30 | 31 | final List below; 32 | final List above; 33 | 34 | @override 35 | RenderRedirectPointer createRenderObject(BuildContext context) { 36 | return RenderRedirectPointer(below: below, above: above); 37 | } 38 | 39 | @override 40 | void updateRenderObject( 41 | BuildContext context, 42 | RenderRedirectPointer renderObject, 43 | ) { 44 | renderObject 45 | ..below = below 46 | ..above = above; 47 | } 48 | } 49 | 50 | class RenderRedirectPointer extends RenderProxyBox { 51 | RenderRedirectPointer({ 52 | RenderBox? child, 53 | this.below = const [], 54 | this.above = const [], 55 | }) : super(child); 56 | 57 | List below; 58 | List above; 59 | 60 | @override 61 | bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { 62 | for (final key in above.reversed) { 63 | final child = key.currentContext?.findRenderObject() as RenderBox?; 64 | if (child != null) { 65 | final hit = result.addWithPaintTransform( 66 | transform: child.getTransformTo(this), 67 | position: position, 68 | hitTest: (result, position) { 69 | return child.hitTest(result, position: position); 70 | }, 71 | ); 72 | if (hit) { 73 | return true; 74 | } 75 | } 76 | } 77 | if (super.hitTestChildren(result, position: position)) { 78 | return true; 79 | } 80 | for (final key in below.reversed) { 81 | final child = key.currentContext?.findRenderObject() as RenderBox?; 82 | if (child != null) { 83 | final hit = result.addWithPaintTransform( 84 | transform: child.getTransformTo(this), 85 | position: position, 86 | hitTest: (result, position) { 87 | return child.hitTest(result, position: position); 88 | }, 89 | ); 90 | if (hit) { 91 | return true; 92 | } 93 | } 94 | } 95 | return false; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /boxy/lib/src/scale.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../boxy.dart'; 4 | 5 | /// Scales its child by a given factor. 6 | /// 7 | /// Unlike [Transform], this widget will pass adjusted constraints to its child 8 | /// so that it can be laid out at the correct size without overflowing. 9 | class Scale extends StatelessWidget { 10 | const Scale({ 11 | super.key, 12 | required this.child, 13 | required this.scale, 14 | }); 15 | 16 | final Widget child; 17 | final double scale; 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return CustomBoxy( 22 | delegate: _ScaleBoxy(scale), 23 | children: [child], 24 | ); 25 | } 26 | } 27 | 28 | class _ScaleBoxy extends BoxyDelegate { 29 | _ScaleBoxy(this.scale); 30 | 31 | final double scale; 32 | 33 | @override 34 | bool shouldRelayout(_ScaleBoxy oldDelegate) => scale != oldDelegate.scale; 35 | 36 | @override 37 | Size layout() { 38 | final child = children.single; 39 | if (scale == 0 || !scale.isFinite) { 40 | child.layout(BoxConstraints.tight(Size.zero)); 41 | return Size.zero; 42 | } 43 | child.setTransform(Matrix4.identity()..scale(scale)); 44 | return child.layout(constraints / scale) * scale; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /boxy/lib/src/sliver_axis_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/rendering.dart'; 2 | import 'sliver_offset.dart'; 3 | 4 | /// Extension on [SliverConstraints] that provides various utilities. 5 | extension SliverConstraintsUtil on SliverConstraints { 6 | /// Whether or not growth happens on the forward direction of [axis]. 7 | bool get growsForward => normalizedGrowthDirection == GrowthDirection.forward; 8 | 9 | /// Whether or not growth happens on the reverse direction of [axis]. 10 | bool get growsReverse => normalizedGrowthDirection == GrowthDirection.reverse; 11 | 12 | /// Returns the relative offset given a cross position, main position, and 13 | /// size. 14 | SliverOffset unwrap(double cross, double main, Size size) { 15 | switch (axis) { 16 | case Axis.horizontal: 17 | if (growsReverse) { 18 | main = size.width - main; 19 | } 20 | return SliverOffset(main, cross, cross, main); 21 | case Axis.vertical: 22 | if (growsReverse) { 23 | main = size.height - main; 24 | } 25 | return SliverOffset(cross, main, cross, main); 26 | } 27 | } 28 | 29 | /// Returns the sliver offset given a regular offset and size. 30 | SliverOffset wrap(Offset offset, Size size) { 31 | switch (axis) { 32 | case Axis.horizontal: 33 | final main = growsReverse ? size.width - offset.dx : offset.dx; 34 | return SliverOffset(offset.dx, offset.dy, offset.dx, main); 35 | case Axis.vertical: 36 | final main = growsReverse ? size.height - offset.dy : offset.dy; 37 | return SliverOffset(offset.dx, offset.dy, offset.dy, main); 38 | } 39 | } 40 | 41 | /// Computes the portion of the region from `from` to `to` that is visible. 42 | /// 43 | /// This method is identical to [RenderSliver.calculatePaintOffset]. 44 | double paintOffset(double from, double to) { 45 | assert(from <= to); 46 | final minOffset = scrollOffset; 47 | final maxOffset = scrollOffset + remainingPaintExtent; 48 | from = from.clamp(minOffset, maxOffset); 49 | to = to.clamp(minOffset, maxOffset); 50 | return (to - from).clamp(0.0, remainingPaintExtent); 51 | } 52 | 53 | /// Computes the portion of the region from `from` to `to` that is within 54 | /// the cache extent of the viewport. 55 | /// 56 | /// This method is identical to [RenderSliver.calculateCacheOffset]. 57 | double cacheOffset(double from, double to) { 58 | assert(from <= to); 59 | final minOffset = scrollOffset + cacheOrigin; 60 | final maxOffset = scrollOffset + remainingCacheExtent; 61 | from = from.clamp(minOffset, maxOffset); 62 | to = to.clamp(minOffset, maxOffset); 63 | // the clamp on the next line is to avoid floating point rounding errors 64 | return (to - from).clamp(0.0, remainingCacheExtent); 65 | } 66 | } 67 | 68 | /// Extension on [RenderSliver] that provides various utilities. 69 | extension RenderSliverUtil on RenderSliver { 70 | /// The current layout size of this sliver. 71 | SliverSize get layoutSize => SliverSize.axis( 72 | constraints.crossAxisExtent, 73 | geometry!.layoutExtent, 74 | constraints.axis, 75 | ); 76 | 77 | /// The current hit testing size of this sliver. 78 | SliverSize get hitTestSize => SliverSize.axis( 79 | constraints.crossAxisExtent, 80 | geometry!.hitTestExtent, 81 | constraints.axis, 82 | ); 83 | } 84 | -------------------------------------------------------------------------------- /boxy/lib/src/sliver_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'sliver_container.dart'; 4 | 5 | /// A sliver container that makes its child look like its inside of a [Card]. 6 | /// 7 | /// To clip the child, add `clipBehavior: Clip.antiAlias`. 8 | /// 9 | /// See also: 10 | /// 11 | /// * [SliverContainer], the sliver this is based on. 12 | class SliverCard extends StatelessWidget { 13 | /// The color of this card. 14 | final Color? color; 15 | 16 | /// The color the cards shadow will cast. 17 | final Color? shadowColor; 18 | 19 | /// The elevation of this card. 20 | final double? elevation; 21 | 22 | /// The shape of this card's material. 23 | final ShapeBorder? shape; 24 | 25 | /// The clip behavior this child will apply to its child, defaults to none. 26 | final Clip? clipBehavior; 27 | 28 | /// The padding to apply around the card. 29 | final EdgeInsetsGeometry? margin; 30 | 31 | /// The sliver child of this card. 32 | final Widget? sliver; 33 | 34 | /// How far the card will extend off-screen when parts of [sliver] are not 35 | /// visible, this should be at least the size of any border effects. 36 | final double? bufferExtent; 37 | 38 | /// Creates a SliverCard. 39 | const SliverCard({ 40 | super.key, 41 | this.color, 42 | this.shadowColor, 43 | this.elevation, 44 | this.shape, 45 | this.margin, 46 | this.clipBehavior, 47 | this.sliver, 48 | this.bufferExtent, 49 | }); 50 | 51 | @override 52 | Widget build(context) { 53 | final textDirection = Directionality.of(context); 54 | final cardTheme = CardTheme.of(context); 55 | final appliedClip = clipBehavior ?? cardTheme.clipBehavior ?? Clip.none; 56 | final appliedMargin = 57 | (margin ?? cardTheme.margin ?? const EdgeInsets.all(4.0)) 58 | .resolve(textDirection); 59 | final appliedShape = shape ?? 60 | cardTheme.shape ?? 61 | const RoundedRectangleBorder( 62 | borderRadius: BorderRadius.all(Radius.circular(4.0)), 63 | ); 64 | final appliedBufferExtent = bufferExtent ?? 12.0; 65 | 66 | final card = Material( 67 | type: MaterialType.card, 68 | shadowColor: shadowColor ?? cardTheme.shadowColor ?? Colors.black, 69 | color: color ?? cardTheme.color ?? Theme.of(context).cardColor, 70 | elevation: elevation ?? cardTheme.elevation ?? 1.0, 71 | shape: appliedShape, 72 | ); 73 | 74 | return SliverContainer( 75 | bufferExtent: appliedBufferExtent, 76 | sliver: sliver, 77 | background: card, 78 | margin: appliedMargin, 79 | clipper: appliedClip == Clip.none 80 | ? null 81 | : ShapeBorderClipper(shape: appliedShape), 82 | clipSliverOnly: true, 83 | ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /boxy/lib/src/sliver_offset.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/rendering.dart'; 2 | 3 | /// Subclass of [Offset] that is also aware of its main and cross axis extent. 4 | class SliverOffset extends Offset { 5 | /// The cross axis position of this offset. 6 | final double cross; 7 | 8 | /// The main axis position of this offset. 9 | final double main; 10 | 11 | /// Creates an offset with cross and main extents. 12 | const SliverOffset(super.dx, super.dy, this.cross, this.main); 13 | 14 | /// Creates an offset with cross and main extents. 15 | SliverOffset.from(Offset offset, {Axis axis = Axis.vertical}) 16 | : cross = axis == Axis.vertical ? offset.dx : offset.dy, 17 | main = axis == Axis.vertical ? offset.dy : offset.dx, 18 | super(offset.dx, offset.dy); 19 | } 20 | 21 | /// Subclass of [Size] that is also aware of its main and cross axis extent. 22 | class SliverSize extends Size { 23 | /// An empty size, one with a zero width and a zero height. 24 | static const zero = SliverSize(0.0, 0.0, Axis.vertical); 25 | 26 | /// The axis of this size. 27 | final Axis axis; 28 | 29 | /// Creates a size with a width, height, and axis. 30 | const SliverSize(super.width, super.height, this.axis); 31 | 32 | /// Creates a size with cross and main extents. 33 | const SliverSize.axis(double cross, double main, this.axis) 34 | : super( 35 | axis == Axis.vertical ? cross : main, 36 | axis == Axis.vertical ? main : cross, 37 | ); 38 | 39 | /// The cross axis extent of this size. 40 | double get cross => axis == Axis.vertical ? width : height; 41 | 42 | /// The main axis extent of this size. 43 | double get main => axis == Axis.vertical ? height : width; 44 | } 45 | -------------------------------------------------------------------------------- /boxy/lib/utils.dart: -------------------------------------------------------------------------------- 1 | /// {@canonicalFor sliver_offset.SliverSize} 2 | /// {@canonicalFor sliver_offset.SliverOffset} 3 | /// This library contains dozens of utility methods for making [Axis]-dependant 4 | /// layouts, including slivers, significantly less verbose. 5 | library utils; 6 | 7 | export 'src/axis_utils.dart'; 8 | export 'src/sliver_axis_utils.dart'; 9 | export 'src/sliver_offset.dart'; 10 | -------------------------------------------------------------------------------- /boxy/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: boxy 2 | description: Overcome limitations of built-in layouts, advanced flex, custom multi-child layouts, slivers, and more! 3 | version: 2.2.1 4 | homepage: https://github.com/pingbird/boxy 5 | documentation: https://boxy.wiki 6 | 7 | environment: 8 | sdk: '>=2.18.0 <4.0.0' 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | vector_math: ^2.1.0 14 | 15 | dev_dependencies: 16 | flutter_test: 17 | sdk: flutter 18 | -------------------------------------------------------------------------------- /boxy/test/boxy_intrinsics_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:boxy/boxy.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | 5 | import 'common.dart'; 6 | 7 | class IntrinsicSizedWrapperBoxy extends BoxyDelegate { 8 | @override 9 | Size layout() { 10 | final child = children.single; 11 | final size = child.layout(constraints); 12 | expect( 13 | child.render.getDistanceToBaseline(TextBaseline.alphabetic), 14 | 11, 15 | ); 16 | return size; 17 | } 18 | } 19 | 20 | class IntrinsicSizedBoxy extends BoxyDelegate { 21 | @override 22 | Size layout() => const Size(1, 2); 23 | 24 | @override 25 | double minIntrinsicWidth(double height) { 26 | expect(height, 3); 27 | return 4; 28 | } 29 | 30 | @override 31 | double minIntrinsicHeight(double width) { 32 | expect(width, 5); 33 | return 6; 34 | } 35 | 36 | @override 37 | double maxIntrinsicWidth(double height) { 38 | expect(height, 7); 39 | return 8; 40 | } 41 | 42 | @override 43 | double maxIntrinsicHeight(double width) { 44 | expect(width, 9); 45 | return 10; 46 | } 47 | 48 | @override 49 | double? distanceToBaseline(TextBaseline baseline) { 50 | return 11; 51 | } 52 | } 53 | 54 | void main() { 55 | testWidgets('Intrinsic sizes', (tester) async { 56 | await tester.pumpWidget( 57 | Center( 58 | child: CustomBoxy( 59 | delegate: IntrinsicSizedWrapperBoxy(), 60 | children: [ 61 | CustomBoxy( 62 | key: const GlobalObjectKey(#boxy), 63 | delegate: IntrinsicSizedBoxy(), 64 | ), 65 | ], 66 | ), 67 | ), 68 | ); 69 | final box = keyBox(#boxy); 70 | expect(box.size, const Size(1, 2)); 71 | expect(box.getMinIntrinsicWidth(3), 4); 72 | expect(box.getMinIntrinsicHeight(5), 6); 73 | expect(box.getMaxIntrinsicWidth(7), 8); 74 | expect(box.getMaxIntrinsicHeight(9), 10); 75 | }); 76 | } 77 | -------------------------------------------------------------------------------- /boxy/test/boxy_pointer_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:boxy/boxy.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/rendering.dart'; 4 | import 'package:flutter_test/flutter_test.dart'; 5 | 6 | import 'boxy_sliver_test.dart'; 7 | import 'common.dart'; 8 | 9 | class PointerBoxy extends BoxyDelegate { 10 | PointerBoxy(this._onPointerEvent); 11 | 12 | final void Function( 13 | PointerEvent event, 14 | BoxHitTestEntry entry, 15 | ) _onPointerEvent; 16 | 17 | @override 18 | void onPointerEvent(PointerEvent event, BoxHitTestEntry entry) { 19 | return _onPointerEvent(event, entry); 20 | } 21 | } 22 | 23 | class BoxPointerBoxy extends BoxBoxyDelegate { 24 | BoxPointerBoxy(this._onPointerEvent); 25 | 26 | final void Function( 27 | PointerEvent event, 28 | BoxHitTestEntry entry, 29 | ) _onPointerEvent; 30 | 31 | @override 32 | void onPointerEvent(PointerEvent event, BoxHitTestEntry entry) { 33 | return _onPointerEvent(event, entry); 34 | } 35 | } 36 | 37 | class SliverPointerBoxy extends SliverBoxyDelegate { 38 | SliverPointerBoxy(this._onPointerEvent); 39 | 40 | final void Function( 41 | PointerEvent event, 42 | SliverHitTestEntry entry, 43 | ) _onPointerEvent; 44 | 45 | @override 46 | void onPointerEvent(PointerEvent event, SliverHitTestEntry entry) { 47 | return _onPointerEvent(event, entry); 48 | } 49 | } 50 | 51 | void main() { 52 | testWidgets('BoxyDelegate onPointerEvent', (tester) async { 53 | BoxHitTestEntry? gotEntry; 54 | await tester.pumpWidget( 55 | CustomBoxy( 56 | key: const GlobalObjectKey(#boxy), 57 | delegate: PointerBoxy((event, entry) => gotEntry = entry), 58 | ), 59 | ); 60 | final box = keyBox(#boxy); 61 | final realEntry = BoxHitTestEntry(box, Offset.zero); 62 | box.handleEvent(const PointerDownEvent(), realEntry); 63 | expect(gotEntry, realEntry); 64 | }); 65 | 66 | testWidgets('BoxBoxyDelegate onPointerEvent', (tester) async { 67 | BoxHitTestEntry? gotEntry; 68 | await tester.pumpWidget( 69 | CustomBoxy.box( 70 | key: const GlobalObjectKey(#boxy), 71 | delegate: BoxPointerBoxy((event, entry) => gotEntry = entry), 72 | ), 73 | ); 74 | final box = keyBox(#boxy); 75 | final realEntry = BoxHitTestEntry(box, Offset.zero); 76 | box.handleEvent(const PointerDownEvent(), realEntry); 77 | expect(gotEntry, realEntry); 78 | }); 79 | 80 | testWidgets('SliverBoxyDelegate onPointerEvent', (tester) async { 81 | SliverHitTestEntry? gotEntry; 82 | await tester.pumpWidget( 83 | CustomBoxy.box( 84 | delegate: BoxToSliverAdapterBoxy(), 85 | children: [ 86 | CustomBoxy.sliver( 87 | key: const GlobalObjectKey(#boxy), 88 | delegate: SliverPointerBoxy((event, entry) => gotEntry = entry), 89 | ), 90 | ], 91 | ), 92 | ); 93 | final sliver = keySliver(#boxy); 94 | final realEntry = SliverHitTestEntry( 95 | sliver, 96 | crossAxisPosition: 0, 97 | mainAxisPosition: 0, 98 | ); 99 | sliver.handleEvent(const PointerDownEvent(), realEntry); 100 | expect(gotEntry, realEntry); 101 | }); 102 | } 103 | -------------------------------------------------------------------------------- /boxy/test/build_scope_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:boxy/boxy.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | 5 | class ProxyBoxy extends BoxyDelegate {} 6 | 7 | void main() { 8 | // Viewports call buildScope, which can fail if we call layout inside of our 9 | // own buildScope for inflation. 10 | testWidgets('CustomScrollView child smoketest', (tester) async { 11 | await tester.pumpWidget( 12 | MaterialApp( 13 | home: CustomBoxy( 14 | delegate: ProxyBoxy(), 15 | children: [ 16 | CustomScrollView( 17 | slivers: [ 18 | SliverList( 19 | delegate: SliverChildListDelegate([ 20 | for (int i = 0; i < 10; i++) Text('$i'), 21 | ]), 22 | ), 23 | ], 24 | ), 25 | BoxyId( 26 | id: #namedList, 27 | child: CustomScrollView( 28 | slivers: [ 29 | SliverList( 30 | delegate: SliverChildListDelegate([ 31 | for (int i = 0; i < 10; i++) Text('$i'), 32 | ]), 33 | ), 34 | ], 35 | ), 36 | ), 37 | ], 38 | ), 39 | ), 40 | ); 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /boxy/test/common.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/rendering.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | 5 | Element keyElement(Object value) { 6 | final results = find.byKey(GlobalObjectKey(value)); 7 | expect(results, findsOneWidget); 8 | return results.evaluate().first; 9 | } 10 | 11 | RenderBox keyBox(Object value) => keyElement(value).renderObject! as RenderBox; 12 | 13 | RenderSliver keySliver(Object value) => 14 | keyElement(value).renderObject! as RenderSliver; 15 | 16 | T keyWidget(Object value) { 17 | final widget = keyElement(value).widget; 18 | expect(widget, isA()); 19 | return widget as T; 20 | } 21 | 22 | Rect boxRect(RenderBox box) { 23 | return Rect.fromPoints( 24 | box.localToGlobal(Offset.zero), 25 | box.localToGlobal(Offset( 26 | box.size.width, 27 | box.size.height, 28 | )), 29 | ); 30 | } 31 | 32 | class TestFrame extends StatelessWidget { 33 | final Widget child; 34 | final BoxConstraints constraints; 35 | 36 | const TestFrame({ 37 | super.key, 38 | required this.child, 39 | this.constraints = const BoxConstraints(), 40 | }); 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | return DefaultTextStyle( 45 | style: Typography.material2014().black.bodyMedium!, 46 | child: Directionality( 47 | textDirection: TextDirection.ltr, 48 | child: OverflowBox( 49 | alignment: Alignment.topLeft, 50 | minWidth: 0, 51 | maxWidth: double.infinity, 52 | minHeight: 0, 53 | maxHeight: double.infinity, 54 | child: ConstrainedBox(constraints: constraints, child: child), 55 | ), 56 | ), 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /boxy/test/dry_layout_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:boxy/boxy.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | 5 | import 'common.dart'; 6 | 7 | class DryTestDelegate extends BoxyDelegate { 8 | @override 9 | Size layout() { 10 | return getChild(0).layout(constraints); 11 | } 12 | } 13 | 14 | void main() { 15 | testWidgets('CustomBoxy can compute dry layout', (tester) async { 16 | await tester.pumpWidget( 17 | Center( 18 | child: CustomBoxy( 19 | key: const GlobalObjectKey(#boxy), 20 | delegate: DryTestDelegate(), 21 | children: const [ 22 | SizedBox( 23 | key: GlobalObjectKey(#child), 24 | width: 100, 25 | height: 100, 26 | ), 27 | ], 28 | ), 29 | ), 30 | ); 31 | 32 | final renderBoxy = keyBox(#boxy); 33 | 34 | // Regular layout 35 | expect(renderBoxy.size, equals(const Size(100, 100))); 36 | 37 | // Compute dry layout with different constraints 38 | expect( 39 | renderBoxy.getDryLayout(const BoxConstraints.tightFor(width: 200)), 40 | equals(const Size(200, 100)), 41 | ); 42 | 43 | // Actual size of boxy and child should remain the same 44 | expect(renderBoxy.size, equals(const Size(100, 100))); 45 | 46 | expect( 47 | keyBox(#child).size, 48 | equals(const Size(100, 100)), 49 | ); 50 | 51 | // Relayout shouldn't cause issues 52 | await tester.pumpWidget( 53 | Center( 54 | child: CustomBoxy( 55 | key: const GlobalObjectKey(#boxy), 56 | delegate: DryTestDelegate(), 57 | children: const [ 58 | SizedBox( 59 | key: GlobalObjectKey(#child), 60 | width: 100, 61 | height: 200, 62 | ), 63 | ], 64 | ), 65 | ), 66 | ); 67 | 68 | expect(renderBoxy.size, equals(const Size(100, 200))); 69 | 70 | expect( 71 | keyBox(#child).size, 72 | equals(const Size(100, 200)), 73 | ); 74 | }); 75 | } 76 | -------------------------------------------------------------------------------- /boxy/test/layers_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:boxy/boxy.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/rendering.dart'; 4 | import 'package:flutter_test/flutter_test.dart'; 5 | 6 | class OpacityBoxy extends BoxyDelegate { 7 | final ValueNotifier opacity; 8 | 9 | OpacityBoxy({ 10 | required this.opacity, 11 | }) : super(repaint: opacity); 12 | 13 | @override 14 | Size layout() { 15 | layoutData ??= LayerKey(); 16 | return super.layout(); 17 | } 18 | 19 | LayerKey get layerKey => layoutData! as LayerKey; 20 | 21 | @override 22 | void paintChildren() { 23 | layers.opacity(opacity: opacity.value, paint: super.paint, key: layerKey); 24 | } 25 | 26 | @override 27 | bool shouldRepaint(OpacityBoxy oldDelegate) => opacity != oldDelegate.opacity; 28 | } 29 | 30 | void main() { 31 | testWidgets('Opacity test', (tester) async { 32 | final opacity = ValueNotifier(1.0); 33 | 34 | await tester.pumpWidget( 35 | CustomBoxy( 36 | key: const GlobalObjectKey(#boxy), 37 | delegate: OpacityBoxy(opacity: opacity), 38 | children: const [ 39 | DecoratedBox( 40 | decoration: BoxDecoration(color: Colors.blue), 41 | child: SizedBox(width: 10, height: 10), 42 | ), 43 | ], 44 | ), 45 | ); 46 | 47 | OpacityLayer getLayer() { 48 | final rootLayer = 49 | // ignore: invalid_use_of_protected_member 50 | RendererBinding.instance.renderViews.single.layer! as TransformLayer; 51 | final pictureLayer = rootLayer.firstChild! as PictureLayer; 52 | return pictureLayer.nextSibling! as OpacityLayer; 53 | } 54 | 55 | final originalLayer = getLayer(); 56 | expect(originalLayer.alpha, 255); 57 | 58 | opacity.value = 0.5; 59 | await tester.pumpAndSettle(); 60 | 61 | final newLayer = getLayer(); 62 | expect(newLayer, originalLayer); 63 | expect(newLayer.alpha, 128); 64 | }); 65 | } 66 | -------------------------------------------------------------------------------- /boxy/test/parent_data_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:boxy/boxy.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | 5 | class ParentDataBoxy extends BoxyDelegate { 6 | ParentDataBoxy(this.onLayoutChild); 7 | 8 | final void Function(dynamic parentData) onLayoutChild; 9 | 10 | @override 11 | Size layout() { 12 | final child = children.single; 13 | onLayoutChild(child.parentData); 14 | child.layout(constraints.loosen()); 15 | return constraints.biggest; 16 | } 17 | } 18 | 19 | void main() { 20 | testWidgets('BoxyId.data', (tester) async { 21 | final expectedParentData = ValueNotifier(0); 22 | var didLayout = false; 23 | 24 | void onLayoutChild(dynamic parentData) { 25 | expect(didLayout, isFalse); 26 | didLayout = true; 27 | expect(parentData, expectedParentData.value); 28 | } 29 | 30 | await tester.pumpWidget( 31 | CustomBoxy( 32 | delegate: ParentDataBoxy(onLayoutChild), 33 | children: [ 34 | AnimatedBuilder( 35 | animation: expectedParentData, 36 | builder: (context, child) { 37 | return BoxyId( 38 | id: 'testId', 39 | data: expectedParentData.value, 40 | child: SizedBox(width: expectedParentData.value.toDouble()), 41 | ); 42 | }, 43 | ) 44 | ], 45 | ), 46 | ); 47 | 48 | // First layout 49 | expect(didLayout, isTrue); 50 | didLayout = false; 51 | 52 | // Second layout with new value 53 | expectedParentData.value++; 54 | await tester.pumpAndSettle(); 55 | expect(didLayout, isTrue); 56 | }); 57 | } 58 | -------------------------------------------------------------------------------- /boxy/test/redirect_pointer_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:boxy/redirect_pointer.dart'; 2 | import 'package:boxy/scale.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_test/flutter_test.dart'; 5 | 6 | import 'common.dart'; 7 | 8 | void main() { 9 | testWidgets('RedirectPointer - Basic', (tester) async { 10 | final taps = []; 11 | final key = GlobalKey(); 12 | await tester.pumpWidget(TestFrame( 13 | constraints: BoxConstraints.tight(const Size(300, 300)), 14 | child: RedirectPointer( 15 | above: [key], 16 | child: Center( 17 | child: SizedBox( 18 | width: 100, 19 | height: 100, 20 | child: Stack( 21 | children: [ 22 | Positioned( 23 | left: -100, 24 | child: IgnorePointer( 25 | child: Container( 26 | key: key, 27 | width: 50, 28 | height: 50, 29 | color: Colors.red, 30 | child: GestureDetector( 31 | onTapDown: taps.add, 32 | ), 33 | ), 34 | ), 35 | ), 36 | ], 37 | ), 38 | ), 39 | ), 40 | ), 41 | )); 42 | await tester.tapAt(tester.getCenter(find.byKey(key))); 43 | expect(taps, hasLength(1)); 44 | expect(taps.single.globalPosition, equals(const Offset(25, 125))); 45 | expect(taps.single.localPosition, equals(const Offset(25, 25))); 46 | }); 47 | 48 | testWidgets('RedirectPointer - Scaled', (tester) async { 49 | final taps = []; 50 | final key = GlobalKey(); 51 | await tester.pumpWidget(TestFrame( 52 | constraints: BoxConstraints.tight(const Size(300, 300)), 53 | child: RedirectPointer( 54 | below: [key], 55 | child: Center( 56 | child: SizedBox( 57 | width: 100, 58 | height: 100, 59 | child: Stack( 60 | children: [ 61 | Positioned( 62 | top: -100, 63 | child: Scale( 64 | scale: 2, 65 | child: IgnorePointer( 66 | child: Container( 67 | key: key, 68 | width: 50, 69 | height: 50, 70 | color: Colors.red, 71 | child: GestureDetector( 72 | onTapDown: taps.add, 73 | ), 74 | ), 75 | ), 76 | ), 77 | ), 78 | ], 79 | ), 80 | ), 81 | ), 82 | ), 83 | )); 84 | await tester.tapAt(tester.getCenter(find.byKey(key))); 85 | expect(taps, hasLength(1)); 86 | expect(taps.single.globalPosition, equals(const Offset(150, 50))); 87 | expect(taps.single.localPosition, equals(const Offset(25, 25))); 88 | }); 89 | } 90 | -------------------------------------------------------------------------------- /boxy/test/simple_column_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:boxy/boxy.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | 5 | import 'common.dart'; 6 | 7 | /// Lays out two children like a column where the second widget is the same 8 | /// width as the first 9 | class SimpleColumnDelegate extends BoxyDelegate { 10 | @override 11 | Size layout() { 12 | // Get both children by a Symbol id. 13 | final firstChild = getChild(#first); 14 | final secondChild = getChild(#second); 15 | 16 | // Lay out the first child with the incoming constraints 17 | final firstSize = firstChild.layout(constraints); 18 | firstChild.position(Offset.zero); 19 | 20 | // Lay out the second child 21 | final secondSize = secondChild.layout(constraints.deflate( 22 | // Subtract height consumed by the first child from the constraints 23 | EdgeInsets.only(top: firstSize.height)).tighten( 24 | // Force width to be the same as the first child 25 | width: firstSize.width)); 26 | 27 | // Position the second child below the first 28 | secondChild.position(Offset(0, firstSize.height)); 29 | 30 | // Calculate the total size based on the size of each child 31 | return Size( 32 | firstSize.width, 33 | firstSize.height + secondSize.height, 34 | ); 35 | } 36 | } 37 | 38 | void main() { 39 | testWidgets('Consistent dimensions', (tester) async { 40 | await tester.pumpWidget(TestFrame( 41 | child: CustomBoxy( 42 | key: const GlobalObjectKey(#boxy), 43 | delegate: SimpleColumnDelegate(), 44 | children: [ 45 | const BoxyId( 46 | id: #first, 47 | child: SizedBox( 48 | key: GlobalObjectKey(#first), 49 | width: 128, 50 | height: 64, 51 | ), 52 | ), 53 | BoxyId( 54 | id: #second, 55 | child: Container( 56 | key: const GlobalObjectKey(#second), 57 | height: 32, 58 | ), 59 | ), 60 | ], 61 | ))); 62 | 63 | final boxyRect = boxRect(keyBox(#boxy)); 64 | final firstRect = boxRect(keyBox(#first)); 65 | final secondRect = boxRect(keyBox(#second)); 66 | 67 | expect(boxyRect, const Rect.fromLTWH(0, 0, 128, 96)); 68 | expect(firstRect, const Rect.fromLTWH(0, 0, 128, 64)); 69 | expect(secondRect, const Rect.fromLTWH(0, 64, 128, 32)); 70 | }); 71 | 72 | testWidgets('Height constraints', (tester) async { 73 | await tester.pumpWidget( 74 | TestFrame( 75 | constraints: const BoxConstraints(maxHeight: 128), 76 | child: CustomBoxy( 77 | key: const GlobalObjectKey(#boxy), 78 | delegate: SimpleColumnDelegate(), 79 | children: [ 80 | const BoxyId( 81 | id: #first, 82 | child: SizedBox( 83 | key: GlobalObjectKey(#first), 84 | width: 128, 85 | height: 64, 86 | )), 87 | BoxyId( 88 | id: #second, 89 | child: Column(children: [ 90 | Expanded( 91 | child: Container( 92 | key: const GlobalObjectKey(#second), 93 | )), 94 | ])), 95 | ], 96 | ), 97 | ), 98 | ); 99 | 100 | final boxyRect = boxRect(keyBox(#boxy)); 101 | final firstRect = boxRect(keyBox(#first)); 102 | final secondRect = boxRect(keyBox(#second)); 103 | 104 | expect(boxyRect, const Rect.fromLTWH(0, 0, 128, 128)); 105 | expect(firstRect, const Rect.fromLTWH(0, 0, 128, 64)); 106 | expect(secondRect, const Rect.fromLTWH(0, 64, 128, 64)); 107 | }); 108 | } 109 | -------------------------------------------------------------------------------- /boxy/test/simple_inflation_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:boxy/boxy.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | 5 | import 'common.dart'; 6 | 7 | class SimpleInflationDelegate extends BoxyDelegate { 8 | @override 9 | Size layout() { 10 | final firstChild = children[0]; 11 | 12 | final firstSize = firstChild.layout(constraints); 13 | firstChild.position(Offset.zero); 14 | 15 | final text = Padding( 16 | padding: const EdgeInsets.all(8), 17 | child: Text( 18 | '^ This guy is ${firstSize.width} x ${firstSize.height}', 19 | key: const GlobalObjectKey(#subtitle), 20 | textAlign: TextAlign.center, 21 | ), 22 | ); 23 | 24 | // Inflate the text widget 25 | final secondChild = inflate(text, id: #subtitle); 26 | 27 | final secondSize = secondChild.layout(constraints 28 | .deflate(EdgeInsets.only(top: firstSize.height)) 29 | .tighten(width: firstSize.width)); 30 | 31 | secondChild.position(Offset(0, firstSize.height)); 32 | 33 | return Size( 34 | firstSize.width, 35 | firstSize.height + secondSize.height, 36 | ); 37 | } 38 | } 39 | 40 | void main() { 41 | testWidgets('Consistent subtitle', (tester) async { 42 | await tester.pumpWidget( 43 | TestFrame( 44 | child: CustomBoxy( 45 | key: const GlobalObjectKey(#boxy), 46 | delegate: SimpleInflationDelegate(), 47 | children: const [ 48 | SizedBox( 49 | width: 100, 50 | height: 50, 51 | ), 52 | ], 53 | ), 54 | ), 55 | ); 56 | 57 | final subtitle = keyWidget(#subtitle).data; 58 | 59 | expect(subtitle, equals('^ This guy is 100.0 x 50.0')); 60 | }); 61 | } 62 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | /site/ 2 | -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | # flutter-boxy-wiki 2 | 3 | This is the source code of https://boxy.wiki 4 | -------------------------------------------------------------------------------- /website/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | * [Welcome](README.md) 4 | 5 | ## ⬜ RenderObjects 6 | 7 | * [Introduction to Layout](renderobjects/introduction-to-layout.md) 8 | * [BoxConstraints](renderobjects/boxconstraints.md) 9 | * [The Many Trees](renderobjects/the-many-trees.md) 10 | * [Interactive Example](renderobjects/interactive-example.md) 11 | * [Learn More](renderobjects/learn-more.md) 12 | 13 | ## ⬜ CustomBoxy 14 | 15 | * [Introduction to CustomBoxy](customboxy/introduction-to-customboxy.md) 16 | * [Hello, World!](customboxy/hello-world.md) 17 | * [BoxyChild](customboxy/boxychild.md) 18 | * [BoxyId](customboxy/boxyid.md) 19 | * [Painting](customboxy/painting.md) 20 | * [Layers](customboxy/advanced-painting.md) 21 | * [Widget Inflation](customboxy/widget-inflation.md) 22 | * [Example: Square Layout](customboxy/example-square-layout.md) 23 | * [Example: Evenly Sized Row](customboxy/example-multiple-children.md) 24 | * [Example: Product Tile](customboxy/example-product-tile.md) 25 | 26 | ## ⬜ Flex 27 | 28 | * [Cross-axis alignment](flex/cross-axis-alignment.md) 29 | * [Dominant](flex/dominant.md) 30 | 31 | ## ⬜ Slivers 32 | 33 | * [SliverContainer](slivers/slivercontainer.md) 34 | * [SliverCard](slivers/slivercard.md) 35 | -------------------------------------------------------------------------------- /website/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | mkdocs build 4 | aws s3 sync site/ s3://boxy-website --delete 5 | aws cloudfront create-invalidation --distribution-id ENR9INMU9EWHN --paths "/*" 6 | -------------------------------------------------------------------------------- /website/docs/assets/beeper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/website/docs/assets/beeper.png -------------------------------------------------------------------------------- /website/docs/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/website/docs/banner.png -------------------------------------------------------------------------------- /website/docs/custom-boxy/boxy-child.md: -------------------------------------------------------------------------------- 1 | # BoxyChild 2 | 3 | To lay out child widgets we first grab a [BoxyChild](https://pub.dev/documentation/boxy/latest/boxy/BoxyChild-class.html) instance, call layout, and optionally position it. 4 | 5 | ```dart 6 | @override 7 | Size layout() { 8 | // Find our children, in this case we assume there is exactly one 9 | final BoxyChild child = children.single; 10 | 11 | // Call layout on the child to obtain their size, we just pass the 12 | // constraints given to the CustomBoxy by its parent 13 | final Size size = child.layout(constraints); 14 | 15 | // Optionally position the child 16 | child.position(Offset.zero); 17 | 18 | // Return a size 19 | return size; 20 | } 21 | ``` 22 | 23 | [BoxyChild](https://pub.dev/documentation/boxy/latest/boxy/BoxyChild-class.html) is just a fancy wrapper around [RenderBox](https://api.flutter.dev/flutter/rendering/RenderBox-class.html), it has a bunch of methods and properties that are useful for layout: 24 | 25 | * [layout](https://pub.dev/documentation/boxy/latest/boxy/BoxyChild/layout.html), a method that lays out the child given some constraints 26 | * [layoutFit](https://pub.dev/documentation/boxy/latest/boxy/BoxyChild/layoutFit.html), a method that lays out and transforms the child according to a [Rect](https://api.dart.dev/stable/2.17.3/dart-ui/Rect-class.html) and [BoxFit](https://api.flutter.dev/flutter/painting/BoxFit.html), just like a [FittedBox](https://api.flutter.dev/flutter/widgets/FittedBox-class.html) 27 | * [layoutRect](https://pub.dev/documentation/boxy/latest/boxy/BoxyChild/layoutRect.html), a method that lays out and positions the child so that it fits a [Rect](https://api.dart.dev/stable/2.17.3/dart-ui/Rect-class.html) with an optional alignment property that behaves like an [Align](https://api.flutter.dev/flutter/widgets/Align-class.html) 28 | * [position](https://pub.dev/documentation/boxy/latest/boxy/BaseBoxyChild/position.html), a method that sets the offset of the child 29 | * [setTransform](https://pub.dev/documentation/boxy/latest/boxy/BaseBoxyChild/setTransform.html), a more advanced version of position that takes a [Matrix4](https://pub.dev/documentation/vector\_math/2.1.2/vector\_math\_64/Matrix4-class.html) transform 30 | * [size](https://pub.dev/documentation/boxy/latest/boxy/BoxyChild/size.html), the size of the child after it's laid out 31 | * [context](https://pub.dev/documentation/boxy/latest/inflating\_element/InflatedChildHandle/context.html), the [Element](https://api.flutter.dev/flutter/widgets/Element-class.html) (aka [BuildContext](https://api.flutter.dev/flutter/widgets/BuildContext-class.html)) of the child 32 | * [id](https://pub.dev/documentation/boxy/latest/inflating\_element/InflatedChildHandle/id.html), the id of the child which is provided by either [BoxyId](https://pub.dev/documentation/boxy/latest/boxy/BoxyId-class.html) or an incrementing integer 33 | -------------------------------------------------------------------------------- /website/docs/custom-boxy/boxy-id.md: -------------------------------------------------------------------------------- 1 | # BoxyId 2 | 3 | ### Identifying children 4 | 5 | In some cases you might want to identify children by name rather than index, [BoxyId](https://pub.dev/documentation/boxy/latest/boxy/BoxyId-class.html) is your friend: 6 | 7 | ![](ftest_XBEjnnpsdS.png) 8 | 9 | ```dart 10 | class MyWidget extends StatelessWidget { 11 | const MyWidget({Key? key}) : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return CustomBoxy( 16 | delegate: MyBoxyDelegate(), 17 | children: const [ 18 | // BoxyId allows children to be accessed by name. 19 | BoxyId( 20 | // This `#hello` is called a Symbol, they are like strings but 21 | // can only be constructed at compile time, making them slightly 22 | // more performant here. 23 | id: #hello, 24 | child: Text('Hello,'), 25 | ), 26 | BoxyId( 27 | id: #world, 28 | child: Text('World!'), 29 | ), 30 | ], 31 | ); 32 | } 33 | } 34 | 35 | class MyBoxyDelegate extends BoxyDelegate { 36 | @override 37 | Size layout() { 38 | // Grab the children by name. 39 | final BoxyChild hello = getChild(#hello); 40 | final BoxyChild world = getChild(#world); 41 | 42 | // Lay them out and store their sizes. 43 | final Size helloSize = hello.layout(constraints); 44 | final Size worldSize = world.layout(constraints); 45 | 46 | // Position the "World!" text below the "Hello,". 47 | world.position(Offset(0, helloSize.height)); 48 | 49 | // Return the size of our little column. 50 | return Size( 51 | max(helloSize.width, worldSize.height), 52 | helloSize.height + worldSize.height, 53 | ); 54 | } 55 | } 56 | ``` 57 | 58 | ### Parent Data 59 | 60 | You can pass data to the delegate using the [data](https://pub.dev/documentation/boxy/latest/boxy/BoxyId/data.html) parameter of [BoxyId](https://pub.dev/documentation/boxy/latest/boxy/BoxyId-class.html). 61 | 62 | This is the same underlying mechanism that [Expanded](https://api.flutter.dev/flutter/widgets/Expanded-class.html) uses to tell the [Row](https://api.flutter.dev/flutter/widgets/Row-class.html) or [Column](https://api.flutter.dev/flutter/widgets/Column-class.html) how much space it should take up. 63 | 64 | ![](image%20(1).png) 65 | 66 | ```dart 67 | class MyWidget extends StatelessWidget { 68 | const MyWidget({Key? key}) : super(key: key); 69 | 70 | @override 71 | Widget build(BuildContext context) { 72 | return CustomBoxy( 73 | delegate: MyBoxyDelegate(), 74 | children: [ 75 | const Text('👻 I am hiding '), 76 | BoxyId( 77 | child: Container( 78 | color: Colors.blue, 79 | width: 50, 80 | height: 50, 81 | ), 82 | // This gets passed to BoxyChild.parentData 83 | data: 0.5, 84 | ), 85 | ], 86 | ); 87 | } 88 | } 89 | 90 | class MyBoxyDelegate extends BoxyDelegate { 91 | @override 92 | void paintChildren() { 93 | children[0].paint(); 94 | layers.opacity( 95 | opacity: children[1].parentData, 96 | paint: children[1].paint, 97 | ); 98 | } 99 | } 100 | ``` 101 | -------------------------------------------------------------------------------- /website/docs/custom-boxy/examples/evenly-sized-row.md: -------------------------------------------------------------------------------- 1 | # Evenly Sized Row 2 | 3 | In this example we take a simple row of widgets and make each of them the same size. This one is a little more 4 | challenging than the previous example because we handle an arbitrary number of children. 5 | 6 | ### Original Layout 7 | 8 | If we throw three text widgets in a Row it looks a little ugly: 9 | 10 | ![](ftest_4KetSQxXfe.png) 11 | 12 | ```dart 13 | class MyWidget extends StatelessWidget { 14 | const MyWidget({Key? key}) : super(key: key); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return Row( 19 | mainAxisAlignment: MainAxisAlignment.center, 20 | children: [ 21 | Material( 22 | borderRadius: const BorderRadius.horizontal( 23 | left: Radius.circular(100), 24 | ), 25 | color: Colors.blue, 26 | child: Container( 27 | padding: const EdgeInsets.all(4.0), 28 | alignment: Alignment.center, 29 | child: const Text('Thing', textAlign: TextAlign.center), 30 | ), 31 | ), 32 | Material( 33 | color: Colors.purple, 34 | child: Container( 35 | padding: const EdgeInsets.all(4.0), 36 | alignment: Alignment.center, 37 | child: const Text( 38 | 'Other thing', 39 | textAlign: TextAlign.center, 40 | ), 41 | ), 42 | ), 43 | Material( 44 | borderRadius: const BorderRadius.horizontal( 45 | right: Radius.circular(100), 46 | ), 47 | color: Colors.pink, 48 | child: Container( 49 | padding: const EdgeInsets.all(4.0), 50 | alignment: Alignment.center, 51 | child: const Text('Other other thing', textAlign: TextAlign.center), 52 | ), 53 | ), 54 | ], 55 | ); 56 | } 57 | } 58 | ``` 59 | 60 | ### Making it look better 61 | 62 | To make this row prettier we want each child to be the same width, fortunately [CustomBoxy](https://pub.dev/documentation/boxy/latest/boxy/CustomBoxy-class.html) allows us to do that: 63 | 64 | ![](ftest_2ETeGIqwH8.png) 65 | 66 | ```dart 67 | class EvenSized extends StatelessWidget { 68 | const EvenSized({ 69 | Key? key, 70 | required this.children, 71 | }) : super(key: key); 72 | 73 | final List children; 74 | 75 | @override 76 | Widget build(BuildContext context) { 77 | return CustomBoxy( 78 | delegate: EvenSizedBoxy(), 79 | children: children, 80 | ); 81 | } 82 | } 83 | 84 | class EvenSizedBoxy extends BoxyDelegate { 85 | @override 86 | Size layout() { 87 | // Find the max intrinsic width of each child 88 | // 89 | // Intrinsics are a little scary but `getMaxIntrinsicWidth(double.infinity)` 90 | // just calculates the width of the child as if its maximum height is 91 | // infinite 92 | var childWidth = 0.0; 93 | for (final child in children) { 94 | childWidth = max( 95 | childWidth, 96 | child.render.getMaxIntrinsicWidth(double.infinity), 97 | ); 98 | } 99 | 100 | // Clamp the width so children don't overflow 101 | childWidth = min(childWidth, constraints.maxWidth / children.length); 102 | 103 | // Find the max intrinsic height 104 | // 105 | // We calculate childHeight after childWidth because the height of text 106 | // depends on its width (i.e. wrapping), `getMinIntrinsicHeight(childWidth)` 107 | // calculates what the child's height would be if it's width is childWidth. 108 | var childHeight = 0.0; 109 | for (final child in children) { 110 | childHeight = max( 111 | childHeight, 112 | child.render.getMinIntrinsicHeight(childWidth), 113 | ); 114 | } 115 | 116 | // Force each child to be the same size 117 | final childConstraints = BoxConstraints.tight( 118 | Size(childWidth, childHeight), 119 | ); 120 | 121 | var x = 0.0; 122 | for (final child in children) { 123 | child.layout(childConstraints); 124 | 125 | // Space them out evenly 126 | child.position(Offset(x, 0)); 127 | x += childWidth; 128 | } 129 | 130 | return Size(childWidth * children.length, childHeight); 131 | } 132 | } 133 | ``` 134 | 135 | Constraining the layout's width shows that the children are distributed evenly and don't overflow, the text has correct wrapping behavior as well: 136 | 137 | ![](ftest_pqqiRVzZwz.png) 138 | -------------------------------------------------------------------------------- /website/docs/custom-boxy/examples/ftest_2ETeGIqwH8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/website/docs/custom-boxy/examples/ftest_2ETeGIqwH8.png -------------------------------------------------------------------------------- /website/docs/custom-boxy/examples/ftest_4KetSQxXfe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/website/docs/custom-boxy/examples/ftest_4KetSQxXfe.png -------------------------------------------------------------------------------- /website/docs/custom-boxy/examples/ftest_7Je17IzRnr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/website/docs/custom-boxy/examples/ftest_7Je17IzRnr.png -------------------------------------------------------------------------------- /website/docs/custom-boxy/examples/ftest_GCKLEFXEnu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/website/docs/custom-boxy/examples/ftest_GCKLEFXEnu.png -------------------------------------------------------------------------------- /website/docs/custom-boxy/examples/ftest_MyQB0wRzDZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/website/docs/custom-boxy/examples/ftest_MyQB0wRzDZ.png -------------------------------------------------------------------------------- /website/docs/custom-boxy/examples/ftest_UHm3QStdY7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/website/docs/custom-boxy/examples/ftest_UHm3QStdY7.png -------------------------------------------------------------------------------- /website/docs/custom-boxy/examples/ftest_UTDKLOvcAU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/website/docs/custom-boxy/examples/ftest_UTDKLOvcAU.png -------------------------------------------------------------------------------- /website/docs/custom-boxy/examples/ftest_X0YPu1QG3P.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/website/docs/custom-boxy/examples/ftest_X0YPu1QG3P.png -------------------------------------------------------------------------------- /website/docs/custom-boxy/examples/ftest_pqqiRVzZwz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/website/docs/custom-boxy/examples/ftest_pqqiRVzZwz.png -------------------------------------------------------------------------------- /website/docs/custom-boxy/examples/image (1) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/website/docs/custom-boxy/examples/image (1) (1) (1).png -------------------------------------------------------------------------------- /website/docs/custom-boxy/examples/product-tile.md: -------------------------------------------------------------------------------- 1 | # Product Tile 2 | 3 | This is an interesting layout I stumbled upon when searching StackOverflow, it contains Title, Seller, and Info widgets arranged so that the Seller is positioned above the space between Title and Info. 4 | 5 | Normally for something like this you would use Stack, but that unfortunately doesn't work if each widget is interactable and has a dynamic size. 6 | 7 | ### Complete Example 8 | 9 | ![](ftest_GCKLEFXEnu.png) 10 | 11 | ```dart 12 | class MyWidget extends StatelessWidget { 13 | const MyWidget({Key? key}) : super(key: key); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return ProductTile( 18 | title: Container( 19 | decoration: BoxDecoration( 20 | color: const Color(0xff4881bd), 21 | borderRadius: BorderRadius.circular(8.0), 22 | ), 23 | width: 350, 24 | height: 200, 25 | alignment: Alignment.center, 26 | child: const Text('Title'), 27 | ), 28 | seller: Container( 29 | decoration: BoxDecoration( 30 | color: const Color(0xff5148bd), 31 | border: Border.all(color: darkBlue, width: 8), 32 | shape: BoxShape.circle, 33 | ), 34 | alignment: Alignment.center, 35 | child: const Text('Seller'), 36 | width: 64, 37 | height: 64, 38 | ), 39 | info: Container( 40 | decoration: BoxDecoration( 41 | color: const Color(0xff5148bd), 42 | borderRadius: BorderRadius.circular(8.0), 43 | ), 44 | alignment: Alignment.center, 45 | child: const Text('Info'), 46 | padding: const EdgeInsets.all(16.0), 47 | ), 48 | ); 49 | } 50 | } 51 | 52 | @immutable 53 | class ProductTileStyle { 54 | /// How far to the left the seller is inset 55 | final double sellerInset; 56 | 57 | /// The size of the gap between the title and description 58 | final double gapHeight; 59 | 60 | const ProductTileStyle({ 61 | this.sellerInset = 16.0, 62 | this.gapHeight = 8.0, 63 | }); 64 | 65 | @override 66 | bool operator ==(Object? other) => 67 | identical(this, other) || 68 | (other is ProductTileStyle && 69 | other.sellerInset == sellerInset && 70 | other.gapHeight == gapHeight); 71 | 72 | @override 73 | int get hashCode => hashValues(sellerInset, gapHeight); 74 | } 75 | 76 | class ProductTile extends StatelessWidget { 77 | final Widget title; 78 | final Widget info; 79 | final Widget seller; 80 | final ProductTileStyle style; 81 | 82 | const ProductTile({ 83 | Key? key, 84 | required this.title, 85 | required this.info, 86 | required this.seller, 87 | this.style = const ProductTileStyle(), 88 | }) : super(key: key); 89 | 90 | @override 91 | Widget build(context) { 92 | return CustomBoxy( 93 | delegate: ProductTileDelegate(style: style), 94 | children: [ 95 | // Children are in paint order, put the seller last so it can sit 96 | // above the others 97 | BoxyId(id: #title, child: title), 98 | BoxyId(id: #info, child: info), 99 | BoxyId(id: #seller, child: seller), 100 | ], 101 | ); 102 | } 103 | } 104 | 105 | class ProductTileDelegate extends BoxyDelegate { 106 | final ProductTileStyle style; 107 | 108 | ProductTileDelegate({required this.style}); 109 | 110 | @override 111 | Size layout() { 112 | // We can grab children by name using BoxyId and getChild 113 | final title = getChild(#title); 114 | final seller = getChild(#seller); 115 | final info = getChild(#info); 116 | 117 | // Lay out the seller first so it can provide a minimum height to the title 118 | // and info 119 | final sellerSize = seller.layout(constraints.deflate( 120 | EdgeInsets.only(right: style.sellerInset), 121 | )); 122 | 123 | // Lay out and position the title 124 | final titleSize = title.layout(constraints.copyWith( 125 | minHeight: sellerSize.height / 2 + style.gapHeight / 2, 126 | )); 127 | title.position(Offset.zero); 128 | 129 | // Position the seller at the bottom right of the title, offset to the left 130 | // by sellerInset 131 | seller.position(Offset( 132 | titleSize.width - (sellerSize.width + style.sellerInset), 133 | (titleSize.height - sellerSize.height / 2) + style.gapHeight / 2, 134 | )); 135 | 136 | // Lay out info to match the width of title and position it below the title 137 | final infoSize = info.layout(BoxConstraints( 138 | minHeight: sellerSize.height / 2, 139 | minWidth: titleSize.width, 140 | maxWidth: titleSize.width, 141 | )); 142 | info.position(Offset(0, titleSize.height + style.gapHeight)); 143 | 144 | return Size( 145 | titleSize.width, 146 | titleSize.height + infoSize.height + style.gapHeight, 147 | ); 148 | } 149 | 150 | // Any BoxyDelegate with parameters should always implement shouldRelaout, 151 | // otherwise it won't update when its properties do. 152 | @override 153 | bool shouldRelayout(ProductTileDelegate oldDelegate) => 154 | style != oldDelegate.style; 155 | } 156 | ``` 157 | 158 | With some images and text, we get a finished product tile that is fully adaptive: 159 | 160 | ![default state](ftest_UTDKLOvcAU.png) 161 | 162 | ![expanded title](ftest_UHm3QStdY7.png) 163 | 164 | ![expanded info](ftest_MyQB0wRzDZ.png) 165 | 166 | ![expanded seller](ftest_7Je17IzRnr.png) 167 | 168 | ![constrained width](ftest_X0YPu1QG3P.png) 169 | -------------------------------------------------------------------------------- /website/docs/custom-boxy/examples/simplified_tree_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/website/docs/custom-boxy/examples/simplified_tree_view.png -------------------------------------------------------------------------------- /website/docs/custom-boxy/examples/square-layout.md: -------------------------------------------------------------------------------- 1 | # Square Layout 2 | 3 | In the interactive example of [Introduction To Layout](/primer/interactive-example/) we showed a custom [RenderObject](https://api.flutter.dev/flutter/rendering/RenderObject-class.html) with a single child, let's see how [CustomBoxy](https://pub.dev/documentation/boxy/latest/boxy/CustomBoxy-class.html) can cut down all of the boilerplate. 4 | 5 | The end goal of this example is to take an arbitrarily sized widget, and size itself so that it's width and height are equal. Ideally the child is also centered in the resulting square. 6 | 7 | ### Complete Example 8 | 9 | To make the widget square, we will create a [BoxyDelegate](https://pub.dev/documentation/boxy/latest/boxy/BoxyDelegate-class.html) that overrides [layout](https://pub.dev/documentation/boxy/latest/boxy/BoxyDelegate/layout.html): 10 | 11 | ![](image%20(1)%20(1)%20(1).png) 12 | 13 | ```dart 14 | class Square extends StatelessWidget { 15 | const Square({Key? key}) : super(key: key); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return Container( 20 | // Add a border so we can see the an outline showing the size 21 | decoration: BoxDecoration(border: Border.all(color: Colors.blue)), 22 | child: CustomBoxy( 23 | delegate: SquareBoxyDelegate(), 24 | children: const [ 25 | Text('Hello, World!'), 26 | ], 27 | ), 28 | ); 29 | } 30 | } 31 | 32 | class SquareBoxyDelegate extends BoxyDelegate { 33 | BoxyChild get child => children.single; 34 | 35 | @override 36 | Size layout() { 37 | // Lay out the child, pass through constraints. 38 | final childSize = child.layout(constraints); 39 | 40 | // Calculate the width of our square by getting the max of the child's 41 | // width and height. 42 | final width = max(childSize.width, childSize.height); 43 | 44 | // Position the child in the center 45 | child.position(Offset( 46 | (width - childSize.width) / 2, 47 | (width - childSize.height) / 2, 48 | )); 49 | 50 | // Return our size 51 | return Size.square(width); 52 | 53 | // An alternate way to center the child using inscribe: 54 | // 55 | // final rect = Offset.zero & Size.square(width); 56 | // child.position(Alignment.center.inscribe(childSize, rect).topLeft); 57 | // return rect.size; 58 | } 59 | } 60 | ``` 61 | 62 | There was a little math involved to center the child, otherwise that was easy-peasy! 63 | -------------------------------------------------------------------------------- /website/docs/custom-boxy/ftest_XBEjnnpsdS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/website/docs/custom-boxy/ftest_XBEjnnpsdS.png -------------------------------------------------------------------------------- /website/docs/custom-boxy/ftest_fcR5Z2lEZD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/website/docs/custom-boxy/ftest_fcR5Z2lEZD.png -------------------------------------------------------------------------------- /website/docs/custom-boxy/ftest_frMkXTvID9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/website/docs/custom-boxy/ftest_frMkXTvID9.png -------------------------------------------------------------------------------- /website/docs/custom-boxy/ftest_m3xCKjHuvM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/website/docs/custom-boxy/ftest_m3xCKjHuvM.png -------------------------------------------------------------------------------- /website/docs/custom-boxy/hello-world.md: -------------------------------------------------------------------------------- 1 | # Hello, World! 2 | 3 | To start, create a [CustomBoxy](https://pub.dev/documentation/boxy/latest/boxy/CustomBoxy-class.html) widget and pass it a subclass of [BoxyDelegate](https://pub.dev/documentation/boxy/latest/boxy/BoxyDelegate-class.html): 4 | 5 | ![](image%20(1)%20(1)%20(1)%20(1)%20(1)%20(1).png) 6 | 7 | ```dart 8 | class MyWidget extends StatelessWidget { 9 | const MyWidget({Key? key}) : super(key: key); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return CustomBoxy( 14 | delegate: MyBoxyDelegate(), 15 | children: const [ 16 | Text('Hello, World!'), 17 | ], 18 | ); 19 | } 20 | } 21 | 22 | class MyBoxyDelegate extends BoxyDelegate {} 23 | ``` 24 | 25 | [CustomBoxy](https://pub.dev/documentation/boxy/latest/boxy/CustomBoxy-class.html) accepts a list of children, it's default behavior is similar to a [Stack](https://api.flutter.dev/flutter/widgets/Stack-class.html), passing through the constraints to each child and sizing itself to the biggest one. 26 | 27 | There are many methods you can override in [BoxyDelegate](https://pub.dev/documentation/boxy/latest/boxy/BoxyDelegate-class.html), the most common being [layout](https://pub.dev/documentation/boxy/latest/boxy/BoxyDelegate/layout.html). 28 | 29 | ### Custom Layout 30 | 31 | 32 | 33 | To customize layout, override the [layout](https://pub.dev/documentation/boxy/latest/boxy/BoxyDelegate/layout.html) method to return a size: 34 | 35 | ```dart 36 | class MyBoxyDelegate extends BoxyDelegate { 37 | @override 38 | // Choose the smallest size that our constraints allow, just like 39 | // an empty SizedBox(). 40 | Size layout() => constraints.smallest; 41 | } 42 | ``` 43 | 44 | [BoxyDelegate](https://pub.dev/documentation/boxy/latest/boxy/BoxyDelegate-class.html) includes several getters that are useful for layout, including: 45 | 46 | * [constraints](https://pub.dev/documentation/boxy/latest/render\_boxy/BoxBoxyDelegateMixin/constraints.html), the [BoxConstraints](https://api.flutter.dev/flutter/rendering/BoxConstraints-class.html) provided by the parent 47 | * [children](https://pub.dev/documentation/boxy/latest/render\_boxy/BaseBoxyDelegate/children.html), a list of [BoxyChild](https://pub.dev/documentation/boxy/latest/boxy/BoxyChild-class.html) instances which let you interact with things passed to [CustomBoxy](https://pub.dev/documentation/boxy/latest/boxy/CustomBoxy-class.html) 48 | * [getChild](https://pub.dev/documentation/boxy/latest/render\_boxy/BaseBoxyDelegate/getChild.html), a method that can get a child by id using [BoxyId](https://pub.dev/documentation/boxy/latest/boxy/BoxyId-class.html) 49 | * [hasChild](https://pub.dev/documentation/boxy/latest/render\_boxy/BaseBoxyDelegate/hasChild.html), a method that returns true if there is a child with a given id 50 | * [layoutData](https://pub.dev/documentation/boxy/latest/render\_boxy/BaseBoxyDelegate/layoutData.html), a variable you can use to hold additional data created during layout 51 | * [render](https://pub.dev/documentation/boxy/latest/render\_boxy/BoxBoxyDelegateMixin/render.html), a raw reference to the [RenderBoxy](https://pub.dev/documentation/boxy/latest/render\_boxy/RenderBoxy-class.html) of [CustomBoxy](https://pub.dev/documentation/boxy/latest/boxy/CustomBoxy-class.html) 52 | * [buildContext](https://pub.dev/documentation/boxy/latest/render\_boxy/BaseBoxyDelegate/buildContext.html), the [BuildContext](https://api.flutter.dev/flutter/widgets/BuildContext-class.html) of the [CustomBoxy](https://pub.dev/documentation/boxy/latest/boxy/CustomBoxy-class.html). 53 | -------------------------------------------------------------------------------- /website/docs/custom-boxy/image (1) (1) (1) (1) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/website/docs/custom-boxy/image (1) (1) (1) (1) (1) (1).png -------------------------------------------------------------------------------- /website/docs/custom-boxy/image (1) (1) (1) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/website/docs/custom-boxy/image (1) (1) (1) (1) (1).png -------------------------------------------------------------------------------- /website/docs/custom-boxy/image (1) (1) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/website/docs/custom-boxy/image (1) (1) (1) (1).png -------------------------------------------------------------------------------- /website/docs/custom-boxy/image (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/website/docs/custom-boxy/image (1).png -------------------------------------------------------------------------------- /website/docs/custom-boxy/image (3).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/website/docs/custom-boxy/image (3).png -------------------------------------------------------------------------------- /website/docs/custom-boxy/introduction-to-customboxy.md: -------------------------------------------------------------------------------- 1 | # Introduction to CustomBoxy 2 | 3 | [:fontawesome-solid-book: CustomBoxy API Docs](https://pub.dev/documentation/boxy/latest/boxy/CustomBoxy-class.html){ .md-button .md-button--primary .block } 4 | 5 | [CustomBoxy](https://pub.dev/documentation/boxy/latest/boxy/CustomBoxy-class.html) is a widget that uses a delegate to implement a custom [RenderObject](https://api.flutter.dev/flutter/rendering/RenderObject-class.html). 6 | 7 | This is essentially a more powerful version of [CustomMultiChildLayout](https://api.flutter.dev/flutter/widgets/CustomMultiChildLayout-class.html) or [CustomPaint](https://api.flutter.dev/flutter/widgets/CustomPaint-class.html), it allows you to inflate, constrain, and lay out each child manually, it also allows its size to depend on the layout of its children. 8 | 9 | This is overkill in most cases, so before diving in you may want to check if some combination of [Stack](https://api.flutter.dev/flutter/widgets/Stack-class.html), [LayoutBuilder](https://api.flutter.dev/flutter/widgets/LayoutBuilder-class.html), [CustomMultiChildLayout](https://api.flutter.dev/flutter/widgets/CustomMultiChildLayout-class.html), or [Flow](https://api.flutter.dev/flutter/widgets/Flow-class.html) is more suitable. 10 | 11 | ### Sections 12 | 13 | 18 | 19 | 24 | 25 | 30 | 31 | 36 | 37 | 42 | 43 | 48 | 49 | ### Examples 50 | 51 | 56 | 57 | 62 | 63 | 68 | 69 | 74 | -------------------------------------------------------------------------------- /website/docs/custom-boxy/layers.md: -------------------------------------------------------------------------------- 1 | # Layers 2 | 3 | Another way we can customize the way children are painted is with [layers](https://pub.dev/documentation/boxy/latest/render\_boxy/BaseBoxyDelegate/layers.html), this is a fancy wrapper around the low level compositing [Layers](https://api.flutter.dev/flutter/rendering/Layer-class.html) used for widgets like [Opacity](https://api.flutter.dev/flutter/widgets/Opacity-class.html) and [BackdropFilter](https://api.flutter.dev/flutter/widgets/BackdropFilter-class.html). 4 | 5 | ![](image%20(1)%20(1)%20(1)%20(1).png) 6 | 7 | ```dart 8 | class MyWidget extends StatelessWidget { 9 | const MyWidget({Key? key}) : super(key: key); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return CustomBoxy( 14 | delegate: MyBoxyDelegate(), 15 | children: [ 16 | Container( 17 | color: Colors.blue, 18 | width: 48, 19 | height: 48, 20 | ), 21 | ], 22 | ); 23 | } 24 | } 25 | 26 | class MyBoxyDelegate extends BoxyDelegate { 27 | @override 28 | void paintChildren() { 29 | // Make the square blurry 30 | layers.imageFilter( 31 | imageFilter: ImageFilter.blur( 32 | sigmaX: 2, 33 | sigmaY: 2, 34 | tileMode: TileMode.decal, 35 | ), 36 | paint: children.single.paint, 37 | ); 38 | } 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /website/docs/custom-boxy/painting.md: -------------------------------------------------------------------------------- 1 | # Painting 2 | 3 | By overriding [paint](https://pub.dev/documentation/boxy/latest/render\_boxy/BaseBoxyDelegate/paint.html) or [paintForeground](https://pub.dev/documentation/boxy/latest/render\_boxy/BaseBoxyDelegate/paintForeground.html) you can get functionality similar to [CustomPaint](https://api.flutter.dev/flutter/widgets/CustomPaint-class.html): 4 | 5 | ![](ftest_frMkXTvID9.png) 6 | 7 | ```dart 8 | class MyBoxyDelegate extends BoxyDelegate { 9 | @override 10 | Size layout() => const Size(32, 32); 11 | 12 | @override 13 | void paint() { 14 | canvas.drawRect( 15 | Offset.zero & render.size, 16 | Paint()..color = Colors.blue, 17 | ); 18 | } 19 | } 20 | ``` 21 | 22 | The [paint](https://pub.dev/documentation/boxy/latest/render\_boxy/BaseBoxyDelegate/paint.html) and [paintForeground](https://pub.dev/documentation/boxy/latest/render\_boxy/BaseBoxyDelegate/paintForeground.html) methods are the same, but [paintForeground](https://pub.dev/documentation/boxy/latest/render\_boxy/BaseBoxyDelegate/paintForeground.html) is called after [paintChildren](https://api.flutter.dev/flutter/rendering/FlowDelegate/paintChildren.html). 23 | 24 | ### Painting children 25 | 26 | We can customize the way children are painted by overriding [paintChildren](https://api.flutter.dev/flutter/rendering/FlowDelegate/paintChildren.html), this is useful if you want to change their paint order for example: 27 | 28 | ![Without paintChildren](ftest_fcR5Z2lEZD.png) ![With paintChildren](image%20(1)%20(1)%20(1)%20(1)%20(1).png) 29 | 30 | ```dart 31 | class MyWidget extends StatelessWidget { 32 | const MyWidget({Key? key}) : super(key: key); 33 | 34 | @override 35 | Widget build(BuildContext context) { 36 | return CustomBoxy( 37 | delegate: MyBoxyDelegate(), 38 | children: [ 39 | Container( 40 | color: Colors.blue, 41 | width: 48, 42 | height: 48, 43 | ), 44 | Container( 45 | color: Colors.red, 46 | width: 96, 47 | height: 48, 48 | ), 49 | ], 50 | ); 51 | } 52 | } 53 | 54 | class MyBoxyDelegate extends BoxyDelegate { 55 | @override 56 | void paintChildren() { 57 | children[1].paint(); 58 | children[0].paint(); 59 | } 60 | } 61 | ``` 62 | 63 | Note that the [canvas](https://pub.dev/documentation/boxy/latest/render\_boxy/BaseBoxyDelegate/canvas.html) is still available, so we can use [paintChildren](https://api.flutter.dev/flutter/rendering/FlowDelegate/paintChildren.html) to paint things between children: 64 | 65 | ![](image%20(3).png) 66 | 67 | ```dart 68 | class MyBoxyDelegate extends BoxyDelegate { 69 | @override 70 | void paintChildren() { 71 | children[1].paint(); 72 | canvas.save(); 73 | canvas.drawCircle( 74 | // Unlike the paint method, the canvas of paintChildren is not transformed 75 | // into the local coordinate space, so we need to offset by paintOffset. 76 | paintOffset + const Offset(48, 24), 77 | 16, 78 | Paint()..color = Colors.white, 79 | ); 80 | canvas.restore(); 81 | children[0].paint(); 82 | } 83 | } 84 | ``` 85 | -------------------------------------------------------------------------------- /website/docs/custom-boxy/widget-inflation.md: -------------------------------------------------------------------------------- 1 | # Widget Inflation 2 | 3 | The most powerful feature of Boxy is the ability to [inflate](https://pub.dev/documentation/boxy/latest/render\_boxy/BaseBoxyDelegate/inflate.html) arbitrary widgets at layout time. 4 | 5 | In the below example we construct a [Text](https://api.flutter.dev/flutter/widgets/Text-class.html) widget based on the size of another child. This would be extremely difficult to accomplish without Boxy, even using a custom [Element](https://api.flutter.dev/flutter/widgets/Element-class.html) and [RenderObject](https://api.flutter.dev/flutter/rendering/RenderObject-class.html). 6 | 7 | ![](ftest_m3xCKjHuvM.png) 8 | 9 | ```dart 10 | class MyWidget extends StatelessWidget { 11 | const MyWidget({Key? key}) : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return CustomBoxy( 16 | delegate: MyBoxyDelegate(), 17 | children: [ 18 | Container( 19 | color: Colors.blue, 20 | width: 50, 21 | height: 50, 22 | ), 23 | ], 24 | ); 25 | } 26 | } 27 | 28 | class MyBoxyDelegate extends BoxyDelegate { 29 | @override 30 | Size layout() { 31 | // Lay out the container first. 32 | final container = children.single; 33 | final containerSize = container.layout(constraints); 34 | 35 | // Inflate a Text widget based on the containers size. 36 | final text = inflate( 37 | Text('^ $containerSize'), 38 | id: #text, 39 | ); 40 | final textSize = text.layout(constraints); 41 | 42 | // Position the text below the container. 43 | text.position(Offset(0, containerSize.height)); 44 | 45 | return Size( 46 | max(containerSize.width, textSize.width), 47 | containerSize.height + textSize.height, 48 | ); 49 | } 50 | } 51 | ``` 52 | -------------------------------------------------------------------------------- /website/docs/helpers/cross-axis-alignment.md: -------------------------------------------------------------------------------- 1 | # Cross-axis alignment 2 | 3 | One of the limitations of a regular [Row](https://api.flutter.dev/flutter/widgets/Row-class.html) or [Column](https://api.flutter.dev/flutter/widgets/Column-class.html) is not being able to use a different cross-axis alignment for each child. 4 | 5 | [BoxyFlexible.align](https://pub.dev/documentation/boxy/latest/flex/BoxyFlexible/BoxyFlexible.align.html) lets you do exactly that: 6 | 7 | ![BoxyColumn](ftest_yiTCTxZ1s9.png) 8 | 9 | ```dart 10 | class MyWidget extends StatelessWidget { 11 | const MyWidget({Key? key}) : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return SizedBox( 16 | width: 100, 17 | child: BoxyColumn( 18 | mainAxisSize: MainAxisSize.min, 19 | crossAxisAlignment: CrossAxisAlignment.center, 20 | children: [ 21 | BoxyFlexible.align( 22 | child: Container( 23 | width: 50, 24 | height: 25, 25 | color: Colors.blueAccent, 26 | alignment: Alignment.center, 27 | child: const Text('start'), 28 | ), 29 | crossAxisAlignment: CrossAxisAlignment.start, 30 | ), 31 | const SizedBox(height: 4), 32 | BoxyFlexible.align( 33 | child: Container( 34 | width: 50, 35 | height: 25, 36 | color: Colors.purpleAccent, 37 | alignment: Alignment.center, 38 | child: const Text('center'), 39 | ), 40 | crossAxisAlignment: CrossAxisAlignment.center, 41 | ), 42 | const SizedBox(height: 4), 43 | BoxyFlexible.align( 44 | child: Container( 45 | width: 50, 46 | height: 25, 47 | color: Colors.pinkAccent, 48 | alignment: Alignment.center, 49 | child: const Text('end'), 50 | ), 51 | crossAxisAlignment: CrossAxisAlignment.end, 52 | ), 53 | const SizedBox(height: 4), 54 | BoxyFlexible.align( 55 | child: Container( 56 | height: 25, 57 | color: Colors.red, 58 | alignment: Alignment.center, 59 | child: const Text('stretch'), 60 | ), 61 | crossAxisAlignment: CrossAxisAlignment.stretch, 62 | ), 63 | ], 64 | ), 65 | ); 66 | } 67 | } 68 | ``` 69 | -------------------------------------------------------------------------------- /website/docs/helpers/dominant.md: -------------------------------------------------------------------------------- 1 | # Dominant 2 | 3 | The [Dominant](https://pub.dev/documentation/boxy/latest/flex/Dominant-class.html) widget tells a [BoxyRow](https://pub.dev/documentation/boxy/latest/flex/BoxyRow-class.html) or [BoxyColumn](https://pub.dev/documentation/boxy/latest/flex/BoxyColumn-class.html) to constrain every other widget to match its cross-axis size: 4 | 5 | ![](image.png) 6 | 7 | ```dart 8 | class MyWidget extends StatelessWidget { 9 | const MyWidget({Key? key}) : super(key: key); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return BoxyRow( 14 | mainAxisSize: MainAxisSize.min, 15 | crossAxisAlignment: CrossAxisAlignment.center, 16 | children: [ 17 | // Blue container should match the height of the pink one below 18 | Container( 19 | color: Colors.blue, 20 | width: 25, 21 | ), 22 | Dominant( 23 | child: Container( 24 | width: 50, 25 | height: 50, 26 | color: Colors.pink, 27 | ), 28 | ), 29 | ], 30 | ); 31 | } 32 | } 33 | ``` 34 | 35 | Due to the quirky nature of [ParentDataWidgets](https://api.flutter.dev/flutter/widgets/ParentDataWidget-class.html), you can't wrap a [Dominant](https://pub.dev/documentation/boxy/latest/flex/Dominant-class.html) widget inside an [Expanded](https://api.flutter.dev/flutter/widgets/Expanded-class.html) widget. 36 | 37 | To make it expanded, use the alternate [Dominant.expanded](https://pub.dev/documentation/boxy/latest/flex/Dominant/Dominant.expanded.html) constructor: 38 | 39 | ![](ftest_nmZYWRSqsy%20(1).png) 40 | 41 | ```dart 42 | class MyWidget extends StatelessWidget { 43 | const MyWidget({Key? key}) : super(key: key); 44 | 45 | @override 46 | Widget build(BuildContext context) { 47 | return Container( 48 | width: 300, 49 | decoration: BoxDecoration( 50 | border: Border.all(color: Colors.white38), 51 | borderRadius: BorderRadius.circular(2), 52 | ), 53 | padding: const EdgeInsets.all(4.0), 54 | child: BoxyRow( 55 | mainAxisSize: MainAxisSize.min, 56 | children: [ 57 | Container( 58 | color: Colors.blue, 59 | child: const RotatedBox( 60 | quarterTurns: -1, 61 | child: Text('Chapter 1'), 62 | ), 63 | padding: const EdgeInsets.all(2.0), 64 | ), 65 | const SizedBox(width: 8.0), 66 | const Dominant.expanded( 67 | child: Text( 68 | 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed ' 69 | 'do eiusmod tempor incididunt ut labore et dolore magna ' 70 | 'aliqua. Ut enim ad minim veniam, quis nostrud exercitation ' 71 | 'ullamco laboris nisi ut aliquip ex ea commodo consequat.', 72 | ), 73 | ), 74 | ], 75 | ), 76 | ); 77 | } 78 | } 79 | ``` 80 | -------------------------------------------------------------------------------- /website/docs/helpers/ftest_IKQC577sJP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/website/docs/helpers/ftest_IKQC577sJP.png -------------------------------------------------------------------------------- /website/docs/helpers/ftest_jInY0aelEY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/website/docs/helpers/ftest_jInY0aelEY.png -------------------------------------------------------------------------------- /website/docs/helpers/ftest_nmZYWRSqsy (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/website/docs/helpers/ftest_nmZYWRSqsy (1).png -------------------------------------------------------------------------------- /website/docs/helpers/ftest_yiTCTxZ1s9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/website/docs/helpers/ftest_yiTCTxZ1s9.png -------------------------------------------------------------------------------- /website/docs/helpers/image (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/website/docs/helpers/image (1) (1).png -------------------------------------------------------------------------------- /website/docs/helpers/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/website/docs/helpers/image.png -------------------------------------------------------------------------------- /website/docs/helpers/sliver-card.md: -------------------------------------------------------------------------------- 1 | # SliverCard 2 | 3 | [SliverCard](https://pub.dev/documentation/boxy/latest/slivers/SliverCard-class.html) is a [Card](https://api.flutter.dev/flutter/material/Card-class.html) that you can wrap slivers in: 4 | 5 | ![](image%20(1)%20(1).png) 6 | 7 | ```dart 8 | final colors = [ 9 | Colors.purple.shade50, 10 | Colors.purple.shade100, 11 | Colors.purple.shade200, 12 | Colors.purple.shade300, 13 | Colors.purple.shade400, 14 | Colors.purple.shade500, 15 | Colors.purple.shade600, 16 | Colors.purple.shade700, 17 | Colors.purple.shade800, 18 | Colors.purple.shade900, 19 | ]; 20 | 21 | class MyWidget extends StatelessWidget { 22 | const MyWidget({Key? key}) : super(key: key); 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return CustomScrollView( 27 | slivers: [ 28 | for (var color in colors) 29 | SliverPadding( 30 | padding: MediaQuery.of(context).padding, 31 | sliver: SliverCard( 32 | color: color, 33 | margin: const EdgeInsets.symmetric( 34 | horizontal: 16.0, 35 | vertical: 2.0, 36 | ), 37 | sliver: SliverPadding( 38 | padding: const EdgeInsets.all(4.0), 39 | sliver: SliverToBoxAdapter( 40 | child: Text('#${(color.value & 0xFFFFFF).toRadixString(16)}'), 41 | ), 42 | ), 43 | ), 44 | ), 45 | ], 46 | ); 47 | } 48 | } 49 | ``` 50 | -------------------------------------------------------------------------------- /website/docs/helpers/sliver-container.md: -------------------------------------------------------------------------------- 1 | # SliverContainer 2 | 3 | [SliverContainer](https://pub.dev/documentation/boxy/latest/slivers/SliverContainer-class.html) is a sliver that gives its sliver a foreground or background box widget, this is useful if you want a sliver to look and feel like the child of a regular widget. 4 | 5 | In the below example we have two [SliverLists](https://api.flutter.dev/flutter/widgets/SliverList-class.html) with [DecoratedBox](https://api.flutter.dev/flutter/widgets/DecoratedBox-class.html) as a background: 6 | 7 | ![](ftest_jInY0aelEY.png) 8 | 9 | By giving the [CustomScrollView](https://api.flutter.dev/flutter/widgets/CustomScrollView-class.html) a clip of [Clip.none](https://api.flutter.dev/flutter/dart-ui/Clip.html), you can see how this is possible: 10 | 11 | ![Same thing but without clipping](ftest_IKQC577sJP.png) 12 | 13 | The [DecoratedBoxes](https://api.flutter.dev/flutter/widgets/DecoratedBox-class.html) are stretched so that their top and bottom edge are barely out of view, while the contents of [SliverList](https://api.flutter.dev/flutter/widgets/SliverList-class.html) are still lazily built. 14 | 15 | ```dart 16 | final colors = [ 17 | [ 18 | Colors.blue.shade50, 19 | Colors.blue.shade100, 20 | Colors.blue.shade200, 21 | Colors.blue.shade300, 22 | Colors.blue.shade400, 23 | Colors.blue.shade500, 24 | Colors.blue.shade600, 25 | Colors.blue.shade700, 26 | Colors.blue.shade800, 27 | Colors.blue.shade900, 28 | ], 29 | [ 30 | Colors.purple.shade50, 31 | Colors.purple.shade100, 32 | Colors.purple.shade200, 33 | Colors.purple.shade300, 34 | Colors.purple.shade400, 35 | Colors.purple.shade500, 36 | Colors.purple.shade600, 37 | Colors.purple.shade700, 38 | Colors.purple.shade800, 39 | Colors.purple.shade900, 40 | ], 41 | ]; 42 | 43 | class MyWidget extends StatelessWidget { 44 | const MyWidget({Key? key}) : super(key: key); 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | return CustomScrollView( 49 | slivers: [ 50 | for (var swatch in colors) 51 | SliverContainer( 52 | background: DecoratedBox( 53 | decoration: BoxDecoration( 54 | border: Border.all(color: Colors.blue), 55 | borderRadius: BorderRadius.circular(6.0), 56 | color: Colors.black, 57 | ), 58 | ), 59 | bufferExtent: 6.0, 60 | padding: const EdgeInsets.only(bottom: 4.0), 61 | margin: const EdgeInsets.symmetric( 62 | horizontal: 16.0, 63 | vertical: 4.0, 64 | ), 65 | sliver: SliverList( 66 | delegate: SliverChildBuilderDelegate( 67 | (context, index) { 68 | return Container( 69 | decoration: BoxDecoration( 70 | color: swatch[index], 71 | borderRadius: BorderRadius.circular(4.0), 72 | ), 73 | height: 25.0, 74 | margin: const EdgeInsets.only( 75 | left: 4.0, 76 | right: 4.0, 77 | top: 4.0, 78 | ), 79 | ); 80 | }, 81 | childCount: swatch.length, 82 | ), 83 | ), 84 | ), 85 | ], 86 | ); 87 | } 88 | } 89 | ``` 90 | -------------------------------------------------------------------------------- /website/docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | hide: 3 | - navigation 4 | --- 5 | 6 | # 7 | 8 | ![](banner.png) 9 | 10 | Boxy is a Flutter package created to overcome the limitations of built-in layout widgets, it provides utilities for flex, custom multi-child layouts, dynamic widget inflation, slivers, and more! 11 | 12 | This package is ready for production use, it has excellent documentation, test coverage, and passes strict analysis. 13 | 14 |

15 | :fontawesome-solid-book: __API Docs__ 16 | :fontawesome-brands-github: __GitHub__ 17 | :simple-dart: __Pub__ 18 | :fontawesome-brands-discord: __Discord__ 19 |

20 | 21 | ### Getting Started 22 | 23 | To install Boxy, add it to your dependencies in `pubspec.yaml`: 24 | 25 | ```yaml 26 | dependencies: 27 | boxy: ^2.2.1 28 | ``` 29 | 30 | Alternatively, you can also use the pub command: 31 | 32 | ``` 33 | flutter pub add boxy 34 | ``` 35 | 36 | After installing the package and running `flutter pub get`, import one of the top-level libraries: 37 | 38 | ```dart 39 | import 'package:boxy/boxy.dart'; 40 | import 'package:boxy/flex.dart'; 41 | import 'package:boxy/padding.dart'; 42 | import 'package:boxy/slivers.dart'; 43 | import 'package:boxy/utils.dart'; 44 | ``` 45 | 46 | ### Sections 47 | 48 | 53 | 54 | 59 | 60 | 65 | 66 | ### Examples 67 | 68 | 73 | 74 | 79 | 80 | 85 | 86 | 91 | -------------------------------------------------------------------------------- /website/docs/primer/box-constraints.md: -------------------------------------------------------------------------------- 1 | # BoxConstraints 2 | 3 | [BoxConstraints](https://api.flutter.dev/flutter/rendering/BoxConstraints-class.html) define a minimum and maximum length for each axis, by default it has a minimum width / height of 0, and maximum of infinity. 4 | 5 | 6 | ### Tight Constraints 7 | 8 | An axis is said to be **tight** if the minimum and maximum is the same, e.g. `BoxConstraints(minWidth: 10.0, maxWidth: 10.0)` has a tight width. The child will not be able to size itself on that axis. 9 | 10 | [SizedBox](https://api.flutter.dev/flutter/widgets/SizedBox-class.html) is an example of a way to provide tight constraints to a child: 11 | 12 | ```dart 13 | SizedBox( 14 | width: 100, 15 | child: Text('My width is 100, no more, no less.'), 16 | ) 17 | ``` 18 | 19 | ### Loose Constraints 20 | 21 | An axis is said to be **loose** if the minimum is 0. The child will be able to choose its size on that axis assuming the maximum is not also 0. 22 | 23 | [Center](https://api.flutter.dev/flutter/widgets/Center-class.html) is a common way of loosening constraints: 24 | 25 | ```dart 26 | SizedBox( 27 | width: 200, 28 | height: 200, 29 | child: Center( 30 | child: Text('I can be any size I want, as long as its < 200.'), 31 | ), 32 | ) 33 | ``` 34 | 35 | ### Unconstrained / Unbounded Constraints 36 | 37 | An axis is said to be **unbounded** if the maximum is infinity. The child will have to determine its own size on that axis, which can cause issues if the child wants to fill its available space. 38 | 39 | An axis is said to be **unconstrained** if the the minimum is 0 and the maximum is infinity. Unbounded constraints are usually also unconstrained, the child can choose any size it wants. 40 | 41 | The most common way widgets become unconstrained is inside a list, like a [ListView](https://api.flutter.dev/flutter/widgets/ListView-class.html) or [Column](https://api.flutter.dev/flutter/widgets/Column-class.html): 42 | 43 | ```dart 44 | ListView( 45 | children: [ 46 | Text('I have a constrained width, but an unconstrained height.'), 47 | ], 48 | ) 49 | // or 50 | Column( 51 | children: [ 52 | Text('I also have an unconstrained height.'), 53 | ], 54 | ) 55 | ``` 56 | 57 | Problems can happen when a child wants to consume all of the space available, but its constraints don't let it: 58 | 59 | ```dart 60 | ListView( 61 | children: [ 62 | // Oops, the height constraint is loosened by ListView 63 | Column( 64 | children: [ 65 | // This throws an error :( 66 | Expanded(child: Text('I want to be as tall as possible')), 67 | ], 68 | ), 69 | ], 70 | ) 71 | ``` 72 | 73 | ### Quirks and Features 🚗 74 | 75 | One notable quirk is that children are forced to follow the constraints given by their parent, which can be unintuitive sometimes: 76 | 77 | ```dart 78 | SizedBox( 79 | width: 100, 80 | child: SizedBox( 81 | width: 200, 82 | child: Text('Is my width 100 or 200?'), 83 | ), 84 | ) 85 | ``` 86 | 87 | Do you think the width of this text is 100 or 200 pixels wide? If you chose 100, you are correct. 88 | 89 | The implementation of [RenderConstrainedBox](https://api.flutter.dev/flutter/rendering/RenderConstrainedBox-class.html) reveals why: 90 | 91 | ```dart 92 | child!.layout(_additionalConstraints.enforce(constraints), parentUsesSize: true); 93 | ``` 94 | 95 | Before the tight constraints are passed down to the child, it enforces the constraints provided by the SizedBox's parent, otherwise the child would overflow. 96 | -------------------------------------------------------------------------------- /website/docs/primer/image (2).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/website/docs/primer/image (2).png -------------------------------------------------------------------------------- /website/docs/primer/introduction-to-layout.md: -------------------------------------------------------------------------------- 1 | # Introduction to Layout 2 | 3 | When Flutter was created, it set out to provide a render architecture that was simple, performant, and modular. Compare this to [HTML](https://developer.chrome.com/articles/layoutng/) or Android [View](https://developer.android.com/reference/android/view/View)s, which have many complex, slow, and implementation-specific rendering protocols. 4 | 5 | Most of the layout widgets you use in Flutter are actually pretty simple and elegant under the hood, there is rarely any magic, so try not to be intimidated by it! 6 | 7 | ### Constraints go down, Sizes go up 8 | 9 | You can think of layout in Flutter as a bunch of functions that take in BoxConstraints and return a Size: 10 | 11 | ```dart title="(simplification)" 12 | Size layout(BoxConstraints constraints) { 13 | final childSize = child.layout(BoxConstraints())); 14 | child.position(Offset.zero); 15 | return childSize; 16 | } 17 | ``` 18 | 19 | This is called the [RenderBox](https://api.flutter.dev/flutter/rendering/RenderBox-class.html) protocol, it's simplicity is what enables animations in Flutter to outperform native Android and iOS. 20 | 21 | The downside of being simple is that developers have to put in a little extra effort to constrain widgets and avoid those pesky flex overflow, unbounded constraints, and infinite size errors. 22 | 23 | The Flutter team made a great article on the design philosophy / performance implications of RenderObjects: [https://docs.flutter.dev/resources/inside-flutter](https://docs.flutter.dev/resources/inside-flutter) 24 | -------------------------------------------------------------------------------- /website/docs/primer/learn-more.md: -------------------------------------------------------------------------------- 1 | # Learn More 2 | 3 | [:simple-flutter: Understanding Constraints](https://docs.flutter.dev/development/ui/layout/constraints){ .md-button .md-button--primary .block } 4 | 5 | [:simple-flutter: Layout Widgets](https://docs.flutter.dev/development/ui/widgets/layout){ .md-button .md-button--primary .block } 6 | 7 | [:simple-flutter: Layouts in Flutter](https://docs.flutter.dev/development/ui/layout){ .md-button .md-button--primary .block } 8 | 9 | [:simple-gitbook: Widgets - Flutter Internals Wiki](https://flutter.megathink.com/data-model/widgets){ .md-button .md-button--primary .block } 10 | 11 | 12 | -------------------------------------------------------------------------------- /website/docs/primer/the-many-trees.md: -------------------------------------------------------------------------------- 1 | # The Many Trees 2 | 3 | Before creating your own [RenderObject](https://api.flutter.dev/flutter/rendering/RenderObject-class.html), we should understand the relationship between the Widget, Element, and Render trees. 4 | 5 | ![](trees.png) 6 | 7 | You are probably familiar with how [State](https://api.flutter.dev/flutter/widgets/State-class.html) works, it's a persistent instance with methods you can override to know when to initialize, build, and dispose. 8 | 9 | State is actually just a fancy delegate for [ComponentElement](https://api.flutter.dev/flutter/widgets/ComponentElement-class.html) to hide it's ugly internals, other than that [Element](https://api.flutter.dev/flutter/widgets/Element-class.html) and [State](https://api.flutter.dev/flutter/widgets/State-class.html) are essentially the same thing! 10 | 11 | Elements implement [BuildContext](https://api.flutter.dev/flutter/widgets/BuildContext-class.html), which also exists to hide ugly internals. 12 | 13 | The Element tree is formed by recursively calling [Widget.createElement](https://api.flutter.dev/flutter/widgets/Widget/createElement.html), Flutter does the dirty work of reactively mounting, building, reparenting, and unmounting them for you. 14 | 15 | When [RenderObjectElement](https://api.flutter.dev/flutter/widgets/RenderObjectElement-class.html)s are mounted they call [RenderObjectWidget.createRenderObject](https://api.flutter.dev/flutter/widgets/RenderObjectWidget/createRenderObject.html) to create a [RenderObject](https://api.flutter.dev/flutter/rendering/RenderObject-class.html). 16 | 17 | For example, [ColoredBox](https://api.flutter.dev/flutter/widgets/ColoredBox-class.html) is a [SingleChildRenderObjectWidget](https://api.flutter.dev/flutter/widgets/SingleChildRenderObjectWidget-class.html) that creates a [SingleChildRenderObjectElement](https://api.flutter.dev/flutter/widgets/SingleChildRenderObjectElement-class.html), and [Row](https://api.flutter.dev/flutter/widgets/Row-class.html) is a [MultiChildRenderObjectWidget](https://api.flutter.dev/flutter/widgets/MultiChildRenderObjectWidget-class.html) that creates a [MultiChildRenderObjectElement](https://api.flutter.dev/flutter/widgets/MultiChildRenderObjectElement-class.html). 18 | 19 | !!! note 20 | 21 | There is technically no such thing as a "widget tree", widgets are more like user-defined data structures that do not share a root node. People are usually referring to the element tree, since elements form a global tree and provide context. 22 | -------------------------------------------------------------------------------- /website/docs/primer/trees.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pingbird/boxy/9d17f98f88c9d7dcd1f0db2dd4f16ea63d3b8220/website/docs/primer/trees.png -------------------------------------------------------------------------------- /website/docs/stylesheets/boxy.css: -------------------------------------------------------------------------------- 1 | [data-md-color-scheme="slate"] { 2 | /*--md-primary-fg-color: #FF6188;*/ 3 | --md-primary-fg-color: #221f22; 4 | --md-accent-fg-color: #221f22; 5 | --md-typeset-a-color: #ff7295; 6 | --md-default-bg-color: #2d2a2e; 7 | --md-footer-bg-color: #221f22; 8 | --md-footer-bg-color--dark: #221f22; 9 | --md-code-bg-color: #221f22; 10 | } 11 | 12 | .md-typeset a:focus, .md-typeset a:hover { 13 | color: #ff96b1; 14 | } 15 | 16 | .md-nav__link:is(a:focus,label:focus), .md-nav__link:is(a:hover,label:hover) { 17 | color: #fdced8; 18 | } 19 | 20 | .md-typeset a { 21 | font-weight: bold; 22 | } 23 | 24 | .md-typeset .grid.cards>:is(ul,ol)>li:is(:focus-within,:hover), .md-typeset .grid>.card:is(:focus-within,:hover) { 25 | background-color: #2a262a; 26 | transition: border 0s, box-shadow 0s; 27 | } 28 | 29 | .md-typeset .grid .card.md-button.md-button--primary { 30 | border-bottom: 3px #b2b2b2 solid; 31 | } 32 | 33 | .md-typeset .grid .card.md-button.md-button--primary:is(:hover) { 34 | border-bottom: 3px #fff solid; 35 | } 36 | 37 | .md-typeset .grid.cards>:is(ul,ol)>li:is(:focus-within,:hover), .md-typeset .grid>.card:is(:focus-within,:hover) { 38 | box-shadow: none; 39 | } 40 | 41 | .md-typeset .grid.cards>:is(ul,ol)>li:is(:focus-within,:hover), .md-typeset .grid>.card { 42 | border-radius: 4px; 43 | } 44 | 45 | .md-button.md-button--primary .twemoji { 46 | margin-right: 16px; 47 | } 48 | 49 | .md-footer { 50 | margin-top: 30px; 51 | } 52 | 53 | .highlight span.filename { 54 | border-radius: 4px 4px 0 0; 55 | } 56 | 57 | .md-typeset h1 { 58 | color: var(--md-typeset-color); 59 | font-weight: bold; 60 | } 61 | 62 | .boxy-content-card { 63 | margin-block-start: 16px; 64 | margin-block-end: 16px; 65 | border-radius: 4px; 66 | background-position: center; 67 | background-size: cover; 68 | } 69 | 70 | .boxy-content-card>a { 71 | display: block; 72 | border: 2px solid var(--md-default-fg-color--lightest); 73 | border-radius: 4px; 74 | padding: 16px; 75 | overflow: hidden; 76 | position: relative; 77 | color: var(--md-typeset-color) !important; 78 | backdrop-filter: blur(4px) saturate(60%); 79 | } 80 | 81 | .boxy-content-card>a:hover { 82 | color: var(--md-typeset-color) !important; 83 | } 84 | 85 | .boxy-content-card .description { 86 | font-weight: normal; 87 | font-size: 90%; 88 | } 89 | 90 | .md-button.block { 91 | display: block !important; 92 | } 93 | 94 | .md-typeset .md-button:is(:focus,:hover) { 95 | background-color: #2a262a; 96 | border-color: #2a262a; 97 | } 98 | 99 | .md-search-result mark { 100 | color: #ff7295; 101 | } 102 | -------------------------------------------------------------------------------- /website/docs/stylesheets/extra.css: -------------------------------------------------------------------------------- 1 | /* Make tabs smaller and add indicator */ 2 | .md-footer .md-social { 3 | padding: 0; 4 | } 5 | .md-tabs .md-tabs__link { 6 | margin: 0; 7 | } 8 | .md-tabs .md-tabs__item { 9 | height: inherit; 10 | padding: 0; 11 | display: flex; 12 | align-content: stretch; 13 | } 14 | 15 | .md-tabs .md-tabs__item a { 16 | vertical-align: middle; 17 | padding: 0 8px; 18 | margin: 0 8px; 19 | } 20 | .md-tabs .md-tabs__link { 21 | box-sizing: border-box; 22 | border-bottom: 2px solid rgba(0, 0, 0, 0); 23 | } 24 | .md-tabs .md-tabs__link--active { 25 | border-bottom: 2px solid; 26 | } 27 | .md-tabs .md-tabs__list { 28 | height: 48px; 29 | display: flex; 30 | flex-direction: row; 31 | } 32 | 33 | 34 | /* Make content a little wider */ 35 | .md-grid { 36 | max-width: 1300px; 37 | } 38 | 39 | /* Disable breakpoint text scaling */ 40 | html { 41 | font-size: 125%; 42 | } 43 | 44 | /* Bigger logo */ 45 | .md-header__button.md-logo :-webkit-any(img,svg) { 46 | height: 32px; 47 | } 48 | .md-header__button.md-logo { 49 | padding: 0; 50 | } 51 | 52 | /* Make sidebar invisible instead of collapsed when hidden */ 53 | .md-main .md-sidebar--primary[hidden] { 54 | display: initial; 55 | visibility: hidden; 56 | } 57 | 58 | @media screen and (max-width: 76.1875em) { 59 | .md-main .md-sidebar--primary[hidden] { 60 | visibility: visible; 61 | } 62 | } 63 | 64 | [data-md-color-primary="white"] { 65 | --md-button-hover-bg-color: hsla(0, 0, 0, 0.1); 66 | } 67 | 68 | [data-md-color-primary="slate"] { 69 | --md-button-hover-bg-color: hsla(0, 0, 0, 0.1); 70 | } 71 | 72 | /* Better looking buttons */ 73 | .md-typeset .md-button { 74 | border-radius: 4px; 75 | margin: 4px; 76 | color: var(--md-typeset-color); 77 | border-color: var(--md-default-fg-color--lightest); 78 | } 79 | 80 | .md-typeset .md-button:is(:focus,:hover):not(.md-button--primary) { 81 | background-color: var(--md-default-fg-color--lightest); 82 | border-color: var(--md-default-fg-color--lightest); 83 | } 84 | 85 | .md-typeset .md-button.md-button--primary { 86 | color: var(--md-primary-bg-color); 87 | border-color: var(--md-primary-fg-color); 88 | } 89 | 90 | .md-typeset .md-button:is(:focus,:hover) { 91 | background-color: var(--md-primary-fg-color); 92 | border-color: var(--md-default-bg-color--light); 93 | } 94 | 95 | /* Better looking tabs */ 96 | .md-typeset .tabbed-set>input:first-child:checked~.tabbed-labels>:first-child, 97 | .md-typeset .tabbed-set>input:nth-child(2):checked~.tabbed-labels>:nth-child(2), 98 | .md-typeset .tabbed-set>input:nth-child(3):checked~.tabbed-labels>:nth-child(3), 99 | .md-typeset .tabbed-set>input:nth-child(4):checked~.tabbed-labels>:nth-child(4), 100 | .md-typeset .tabbed-set>input:nth-child(5):checked~.tabbed-labels>:nth-child(5), 101 | .md-typeset .tabbed-set>input:nth-child(6):checked~.tabbed-labels>:nth-child(6), 102 | .md-typeset .tabbed-set>input:nth-child(7):checked~.tabbed-labels>:nth-child(7), 103 | .md-typeset .tabbed-set>input:nth-child(8):checked~.tabbed-labels>:nth-child(8), 104 | .md-typeset .tabbed-set>input:nth-child(9):checked~.tabbed-labels>:nth-child(9), 105 | .md-typeset .tabbed-set>input:nth-child(10):checked~.tabbed-labels>:nth-child(10), 106 | .md-typeset .tabbed-set>input:nth-child(11):checked~.tabbed-labels>:nth-child(11), 107 | .md-typeset .tabbed-set>input:nth-child(12):checked~.tabbed-labels>:nth-child(12), 108 | .md-typeset .tabbed-set>input:nth-child(13):checked~.tabbed-labels>:nth-child(13), 109 | .md-typeset .tabbed-set>input:nth-child(14):checked~.tabbed-labels>:nth-child(14), 110 | .md-typeset .tabbed-set>input:nth-child(15):checked~.tabbed-labels>:nth-child(15), 111 | .md-typeset .tabbed-set>input:nth-child(16):checked~.tabbed-labels>:nth-child(16), 112 | .md-typeset .tabbed-set>input:nth-child(17):checked~.tabbed-labels>:nth-child(17), 113 | .md-typeset .tabbed-set>input:nth-child(18):checked~.tabbed-labels>:nth-child(18), 114 | .md-typeset .tabbed-set>input:nth-child(19):checked~.tabbed-labels>:nth-child(19), 115 | .md-typeset .tabbed-set>input:nth-child(20):checked~.tabbed-labels>:nth-child(20) { 116 | color: var(--md-typeset-a-color); 117 | } 118 | .md-typeset .tabbed-labels>label:hover { 119 | color: var(--md-typeset-color--light); 120 | } 121 | .js .md-typeset .tabbed-labels:before { 122 | background: var(--md-typeset-a-color); 123 | } 124 | 125 | /* 🌠 */ 126 | .star-list li { 127 | text-indent: 4px; 128 | list-style-image: url(/docs/assets/star.svg); 129 | } 130 | .star-list li::marker { 131 | font-size: 2rem; 132 | margin: 0; 133 | line-height: 0.5; 134 | } 135 | -------------------------------------------------------------------------------- /website/mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Boxy 2 | site_url: https://boxy.wiki 3 | nav: 4 | - Home: index.md 5 | - Primer: 6 | - primer/introduction-to-layout.md 7 | - primer/box-constraints.md 8 | - primer/the-many-trees.md 9 | - primer/interactive-example.md 10 | - primer/learn-more.md 11 | - CustomBoxy: 12 | - custom-boxy/introduction-to-customboxy.md 13 | - custom-boxy/hello-world.md 14 | - custom-boxy/boxy-child.md 15 | - custom-boxy/boxy-id.md 16 | - custom-boxy/painting.md 17 | - custom-boxy/layers.md 18 | - custom-boxy/widget-inflation.md 19 | - "Example: Square Layout": custom-boxy/examples/square-layout.md 20 | - "Example: Evenly Sized Row": custom-boxy/examples/evenly-sized-row.md 21 | - "Example: Product Tile": custom-boxy/examples/product-tile.md 22 | - "Example: Tree View": custom-boxy/examples/tree-view.md 23 | - Helpers: 24 | - Flex: 25 | - helpers/cross-axis-alignment.md 26 | - helpers/dominant.md 27 | - Slivers: 28 | - helpers/sliver-container.md 29 | - helpers/sliver-card.md 30 | theme: 31 | name: material 32 | custom_dir: overrides 33 | logo: assets/beeper.png 34 | favicon: assets/beeper.png 35 | palette: 36 | - scheme: slate 37 | primary: blue 38 | accent: blue 39 | features: 40 | - navigation.tabs 41 | - navigation.instant 42 | - navigation.expand 43 | - toc.follow 44 | - content.code.annotate 45 | markdown_extensions: 46 | - tables 47 | - admonition 48 | - pymdownx.highlight: 49 | anchor_linenums: true 50 | - pymdownx.emoji: 51 | emoji_index: !!python/name:materialx.emoji.twemoji 52 | emoji_generator: !!python/name:materialx.emoji.to_svg 53 | - pymdownx.inlinehilite 54 | - pymdownx.snippets 55 | - pymdownx.superfences 56 | - pymdownx.tabbed: 57 | alternate_style: true 58 | - attr_list 59 | - md_in_html 60 | - pymdownx.details 61 | extra: 62 | generator: false 63 | social: 64 | - icon: fontawesome/solid/heart 65 | link: https://tst.sh 66 | analytics: 67 | provider: google 68 | property: G-3Q8MDN3K8F 69 | repo_url: https://github.com/PixelToast/flutter-boxy 70 | extra_css: 71 | - stylesheets/extra.css 72 | - stylesheets/boxy.css 73 | -------------------------------------------------------------------------------- /website/overrides/partials/comments.html: -------------------------------------------------------------------------------- 1 | {% if page.meta.comments %} 2 |

{{ lang.t("meta.comments") }}

3 | 18 | 19 | 20 | 48 | {% endif %} -------------------------------------------------------------------------------- /website/overrides/partials/tabs-item.html: -------------------------------------------------------------------------------- 1 | {#- 2 | This file was automatically generated - do not edit 3 | -#} 4 | {% if not class %} 5 | {% set class = "md-tabs__link" %} 6 | {% if nav_item.active %} 7 | {% set class = class ~ " md-tabs__link--active" %} 8 | {% endif %} 9 | {% endif %} 10 | {% if nav_item.children %} 11 | {% set title = title | d(nav_item.title) %} 12 | {% set nav_item = nav_item.children | first %} 13 | {% if nav_item.children %} 14 | {% include "partials/tabs-item.html" %} 15 | {% else %} 16 |
  • 17 | 18 |

    {{ title }}

    19 |
    20 |
  • 21 | {% endif %} 22 | {% else %} 23 |
  • 24 | 25 |

    {{ nav_item.title }}

    26 |
    27 |
  • 28 | {% endif %} 29 | --------------------------------------------------------------------------------