[
  {
    "path": ".github/workflows/readme.yml",
    "content": "name: Check README\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\njobs:\n  # Make sure that the readme has been generated from the `lib.rs` docs\n  # and is not out-of-sync.\n  check-readme:\n    runs-on: ubuntu-latest\n    container:\n      image: ghcr.io/msrd0/cargo-readme:latest\n    steps:\n      - uses: actions/checkout@v2\n      - name: Copy README\n        run: cp README.md README.md.ref\n      - name: Generate README from lib.rs\n        run: cargo readme > README.md\n      - name: Diff Generated README and Copied README\n        run: diff README.md README.md.ref\n"
  },
  {
    "path": ".github/workflows/rust.yml",
    "content": "name: Rust\n\non: [push, pull_request, workflow_dispatch]\n\nenv:\n  CARGO_TERM_COLOR: always\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n    - name: Checkout\n      uses: actions/checkout@v2\n    - name: Install alsa and udev\n      run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev\n    - name: Build\n      run: cargo build --all --features all\n    - name: Run tests\n      run: cargo test --all --features all \n"
  },
  {
    "path": ".github/workflows/website.yml",
    "content": "name: \"Build & Deploy Website\"\non:\n  push:\n    branches:\n      - master \n  pull_request:\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@master\n      - name: Install Just\n        run: curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | sudo bash -s -- --to /usr/local/bin\n      - name: Run Website Doc Tests\n        run: just website-doc-tests\n\n  build:\n    runs-on: ubuntu-latest\n    if: github.ref != 'refs/heads/master'\n    steps:\n      - name: Checkout\n        uses: actions/checkout@master\n      - name: Build only\n        uses: shalzz/zola-deploy-action@master\n        env:\n          BUILD_DIR: site\n          GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN  }}\n          BUILD_ONLY: true\n\n  build_and_deploy:\n    runs-on: ubuntu-latest\n    if: github.ref == 'refs/heads/master'\n    steps:\n      - name: Checkout\n        uses: actions/checkout@master\n      - name: Build and Deploy\n        uses: shalzz/zola-deploy-action@master\n        env:\n          PAGES_BRANCH: gh-pages\n          BUILD_DIR: site\n          GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN  }}"
  },
  {
    "path": ".gitignore",
    "content": "/target\nCargo.lock\n*.sh\n\n*gitignore*\n!.gitignore"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"site/themes/adidoks\"]\n\tpath = site/themes/adidoks\n\turl = https://github.com/RAUI-labs/raui_site_theme.git\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[workspace]\nmembers = [\n    \"crates/*\",\n    \"demos/*\",\n    \"site/rust/guide_*\"\n]\nresolver = \"2\"\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (C) 2025 Patryk 'PsichiX' Budzyński <https://psichix.io/>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n================================================================================\n\nApache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nCopyright (C) 2025 Patryk 'PsichiX' Budzyński <https://psichix.io/>\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction,\nand distribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by\nthe copyright owner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all\nother entities that control, are controlled by, or are under common\ncontrol with that entity. For the purposes of this definition,\n\"control\" means (i) the power, direct or indirect, to cause the\ndirection or management of such entity, whether by contract or\notherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity\nexercising permissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications,\nincluding but not limited to software source code, documentation\nsource, and configuration files.\n\n\"Object\" form shall mean any form resulting from mechanical\ntransformation or translation of a Source form, including but\nnot limited to compiled object code, generated documentation,\nand conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or\nObject form, made available under the License, as indicated by a\ncopyright notice that is included in or attached to the work\n(an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object\nform, that is based on (or derived from) the Work and for which the\neditorial revisions, annotations, elaborations, or other modifications\nrepresent, as a whole, an original work of authorship. For the purposes\nof this License, Derivative Works shall not include works that remain\nseparable from, or merely link (or bind by name) to the interfaces of,\nthe Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including\nthe original version of the Work and any modifications or additions\nto that Work or Derivative Works thereof, that is intentionally\nsubmitted to Licensor for inclusion in the Work by the copyright owner\nor by an individual or Legal Entity authorized to submit on behalf of\nthe copyright owner. For the purposes of this definition, \"submitted\"\nmeans any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems,\nand issue tracking systems that are managed by, or on behalf of, the\nLicensor for the purpose of discussing and improving the Work, but\nexcluding communication that is conspicuously marked or otherwise\ndesignated in writing by the copyright owner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity\non behalf of whom a Contribution has been received by Licensor and\nsubsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of\nthis License, each Contributor hereby grants to You a perpetual,\nworldwide, non-exclusive, no-charge, royalty-free, irrevocable\ncopyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the\nWork and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of\nthis License, each Contributor hereby grants to You a perpetual,\nworldwide, non-exclusive, no-charge, royalty-free, irrevocable\n(except as stated in this section) patent license to make, have made,\nuse, offer to sell, sell, import, and otherwise transfer the Work,\nwhere such license applies only to those patent claims licensable\nby such Contributor that are necessarily infringed by their\nContribution(s) alone or by combination of their Contribution(s)\nwith the Work to which such Contribution(s) was submitted. If You\ninstitute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work\nor a Contribution incorporated within the Work constitutes direct\nor contributory patent infringement, then any patent licenses\ngranted to You under this License for that Work shall terminate\nas of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the\nWork or Derivative Works thereof in any medium, with or without\nmodifications, and in Source or Object form, provided that You\nmeet the following conditions:\n\n(a) You must give any other recipients of the Work or\nDerivative Works a copy of this License; and\n\n(b) You must cause any modified files to carry prominent notices\nstating that You changed the files; and\n\n(c) You must retain, in the Source form of any Derivative Works\nthat You distribute, all copyright, patent, trademark, and\nattribution notices from the Source form of the Work,\nexcluding those notices that do not pertain to any part of\nthe Derivative Works; and\n\n(d) If the Work includes a \"NOTICE\" text file as part of its\ndistribution, then any Derivative Works that You distribute must\ninclude a readable copy of the attribution notices contained\nwithin such NOTICE file, excluding those notices that do not\npertain to any part of the Derivative Works, in at least one\nof the following places: within a NOTICE text file distributed\nas part of the Derivative Works; within the Source form or\ndocumentation, if provided along with the Derivative Works; or,\nwithin a display generated by the Derivative Works, if and\nwherever such third-party notices normally appear. The contents\nof the NOTICE file are for informational purposes only and\ndo not modify the License. You may add Your own attribution\nnotices within Derivative Works that You distribute, alongside\nor as an addendum to the NOTICE text from the Work, provided\nthat such additional attribution notices cannot be construed\nas modifying the License.\n\nYou may add Your own copyright statement to Your modifications and\nmay provide additional or different license terms and conditions\nfor use, reproduction, or distribution of Your modifications, or\nfor any such Derivative Works as a whole, provided Your use,\nreproduction, and distribution of the Work otherwise complies with\nthe conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise,\nany Contribution intentionally submitted for inclusion in the Work\nby You to the Licensor shall be under the terms and conditions of\nthis License, without any additional terms or conditions.\nNotwithstanding the above, nothing herein shall supersede or modify\nthe terms of any separate license agreement you may have executed\nwith Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade\nnames, trademarks, service marks, or product names of the Licensor,\nexcept as required for reasonable and customary use in describing the\norigin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or\nagreed to in writing, Licensor provides the Work (and each\nContributor provides its Contributions) on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\nimplied, including, without limitation, any warranties or conditions\nof TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\nPARTICULAR PURPOSE. You are solely responsible for determining the\nappropriateness of using or redistributing the Work and assume any\nrisks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory,\nwhether in tort (including negligence), contract, or otherwise,\nunless required by applicable law (such as deliberate and grossly\nnegligent acts) or agreed to in writing, shall any Contributor be\nliable to You for damages, including any direct, indirect, special,\nincidental, or consequential damages of any character arising as a\nresult of this License or out of the use or inability to use the\nWork (including but not limited to damages for loss of goodwill,\nwork stoppage, computer failure or malfunction, or any and all\nother commercial damages or losses), even if such Contributor\nhas been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing\nthe Work or Derivative Works thereof, You may choose to offer,\nand charge a fee for, acceptance of support, warranty, indemnity,\nor other liability obligations and/or rights consistent with this\nLicense. However, in accepting such obligations, You may act only\non Your own behalf and on Your sole responsibility, not on behalf\nof any other Contributor, and only if You agree to indemnify,\ndefend, and hold each Contributor harmless for any liability\nincurred by, or claims asserted against, such Contributor by reason\nof your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work.\n\nTo apply the Apache License to your work, attach the following\nboilerplate notice, with the fields enclosed by brackets \"[]\"\nreplaced with your own identifying information. (Don't include\nthe brackets!)  The text should be enclosed in the appropriate\ncomment syntax for the file format. We also recommend that a\nfile or class name and description of purpose be included on the\nsame \"printed page\" as the copyright notice for easier\nidentification within third-party archives.\n\nCopyright [yyyy] [name of copyright owner]\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "# raui\n\nRAUI is a renderer agnostic UI system that is heavily inspired by **React**'s declarative UI\ncomposition and the **Unreal Engine Slate** widget components system.\n\n> 🗣 **Pronunciation:** RAUI is pronounced like **\"ra\"** ( the Egyptian god ) + **\"oui\"**\n> (french for \"yes\" ) — [Audio Example][pronounciation].\n\n[pronounciation]: https://itinerarium.github.io/phoneme-synthesis/?w=/%27rawi/\n\nThe main idea behind RAUI architecture is to treat UI as another data source that you transform\ninto your target renderable data format used by your rendering engine of choice.\n\n## Architecture\n\n### [`Application`]\n\n[`Application`] is the central point of user interest. It performs whole UI processing logic.\nThere you apply widget tree that wil be processed, send messages from host application to\nwidgets and receive signals sent from widgets to host application.\n\n\n### Widgets\n\nWidgets are divided into three categories:\n- **[`WidgetNode`]** - used as source UI trees (variant that can be either a component, unit or\n  none)\n\n\n- **[`WidgetComponent`]** - you can think of them as Virtual DOM nodes, they store:\n  - pointer to _component function_ (that process their data)\n  - unique _key_ (that is a part of widget ID and will be used to tell the system if it should\n    carry its _state_ to next processing run)\n  - boxed cloneable _properties_ data\n  - _listed slots_ (simply: widget children)\n  - _named slots_ (similar to listed slots: widget children, but these ones have names assigned\n    to them, so you can access them by name instead of by index)\n- **[`WidgetUnit`]** - an atomic element that renderers use to convert into target renderable\n  data format for rendering engine of choice.\n\n### Component Function\n\nComponent functions are static functions that transforms input data (properties, state or\nneither of them) into output widget tree (usually used to simply wrap another components tree\nunder one simple component, where at some point the simplest components returns final\n_[`WidgetUnit`]'s_). They work together as a chain of transforms - root component applies some\nproperties into children components using data from its own properties or state.\n\n#### States\n\nThis may bring up a question: _**\"If i use only functions and no objects to tell how to\nvisualize UI, how do i keep some data between each render run?\"**_. For that you use _states_.\nState is a data that is stored between each processing calls as long as given widget is alive\n(that means: as long as widget id stays the same between two processing calls, to make sure your\nwidget stays the same, you use keys - if no key is assigned, system will generate one for your\nwidget but that will make it possible to die at any time if for example number of widget\nchildren changes in your common parent, your widget will change its id when key wasn't\nassigned). Some additional notes: While you use _properties_ to send information down the tree\nand _states_ to store widget data between processing cals, you can communicate with another\nwidgets and host application using messages and signals! More than that, you can use hooks to\nlisten for widget life cycle and perform actions there. It's worth noting that state uses\n_properties_ to hold its data, so by that you can for example attach multiple hooks that each of\nthem uses different data type as widget state, this opens the doors to be very creative when\ncombining different hooks that operate on the same widget.\n\n### Hooks\n\nHooks are used to put common widget logic into separate functions that can be chained in widgets\nand another hooks (you can build a reusable dependency chain of logic with that). Usually it is\nused to listen for life cycle events such as mount, change and unmount, additionally you can\nchain hooks to be processed sequentially in order they are chained in widgets and other hooks.\n\nWhat happens under the hood:\n- Application calls `button` on a node\n    - `button` calls `use_button` hook\n        - `use_button` calls `use_empty` hook\n    - `use_button` logic is executed\n- `button` logic is executed\n\n### Layouting\n\nRAUI exposes the [`Application::layout()`][core::application::Application::layout] API to allow\nuse of virtual-to-real coords mapping and custom layout engines to perform widget tree\npositioning data, which is later used by custom UI renderers to specify boxes where given\nwidgets should be placed. Every call to perform layouting will store a layout data inside\nApplication, you can always access that data at any time. There is a [`DefaultLayoutEngine`]\nthat does this in a generic way. If you find some part of its pipeline working different than\nwhat you've expected, feel free to create your custom layout engine!\n\n### Interactivity\n\nRAUI allows you to ease and automate interactions with UI by use of Interactions Engine - this\nis just a struct that implements [`perform_interactions`] method with reference to Application,\nand all you should do there is to send user input related messages to widgets. There is\n[`DefaultInteractionsEngine`] that covers widget navigation, button and input field - actions\nsent from input devices such as mouse (or any single pointer), keyboard and gamepad. When it\ncomes to UI navigation you can send raw [`NavSignal`] messages to the default interactions\nengine and despite being able to select/unselect widgets at will, you have typical navigation\nactions available: up, down, left, right, previous tab/screen, next tab/screen, also being able\nto focus text inputs and send text input changes to focused input widget. All interactive widget\ncomponents that are provided by RAUI handle all [`NavSignal`] actions in their hooks, so all\nuser has to do is to just activate navigation features for them (using [`NavItemActive`] unit\nprops). RAUI integrations that want to just use use default interactions engine should make use\nof this struct composed in them and call its [`interact`] method with information about what\ninput change was made. There is an example of that feature covered in RAUI App crate\n(`AppInteractionsEngine` struct).\n\n**NOTE: Interactions engines should use layout for pointer events so make sure that you rebuild\nlayout before you perform interactions!**\n\n[`Application`]: core::application::Application\n[`WidgetNode`]: core::widget::node::WidgetNode\n[`WidgetComponent`]: core::widget::component::WidgetComponent\n[`WidgetUnit`]: core::widget::unit::WidgetUnit\n[`DefaultLayoutEngine`]: core::layout::default_layout_engine::DefaultLayoutEngine\n[`NavSignal`]: core::widget::component::interactive::navigation::NavSignal\n[`NavItemActive`]: core::widget::component::interactive::navigation::NavItemActive\n[`perform_interactions`]: core::interactive::InteractionsEngine::perform_interactions\n[`interact`]:\ncore::interactive::default_interactions_engine::DefaultInteractionsEngine::interact\n[`DefaultInteractionsEngine`]:\ncore::interactive::default_interactions_engine::DefaultInteractionsEngine\n\nLicense: MIT OR Apache-2.0\n"
  },
  {
    "path": "README.tpl",
    "content": "# RAUI [![Crates.io](https://img.shields.io/crates/v/raui.svg)](https://crates.io/crates/raui)[![Docs.rs](https://docs.rs/raui/badge.svg)](https://docs.rs/raui)\n\n## About\n\n{{readme}}\n\n<!-- Docs links from the lib.rs doc string -->\n[`Application`]: https://docs.rs/raui/latest/raui/core/application/struct.Application.html\n[`WidgetNode`]: https://docs.rs/raui/latest/raui/core/widget/node/enum.WidgetNode.html\n[`WidgetComponent`]: https://docs.rs/raui/latest/raui/core/widget/component/struct.WidgetComponent.html\n[`WidgetUnit`]: https://docs.rs/raui/latest/raui/core/widget/unit/enum.WidgetUnit.html\n[`DefaultLayoutEngine`]: https://docs.rs/raui/latest/raui/core/layout/default_layout_engine/struct.DefaultLayoutEngine.html\n[`NavSignal`]: https://docs.rs/raui/latest/raui/core/widget/component/interactive/navigation/enum.NavSignal.html\n[`NavItemActive`]: https://docs.rs/raui/latest/raui/core/widget/component/interactive/navigation/struct.NavItemActive.html\n[`perform_interactions`]: https://docs.rs/raui/latest/raui/core/interactive/trait.InteractionsEngine.html#tymethod.perform_interactions\n[`interact`]: https://docs.rs/raui/latest/raui/interactive/struct.DefaultInteractionsEngine.html#method.interact\n[`DefaultInteractionsEngine`]: https://docs.rs/raui/latest/raui/interactive/struct.DefaultInteractionsEngine.html\n\n## Media\n- [`RAUI + Spitfire In-Game`](https://github.com/RAUI-labs/raui/tree/master/demos/in-game)\n  An example of an In-Game integration of RAUI with custom Material theme, using Spitfire as a renderer.\n\n  ![RAUI + Spitfire In-Game](https://github.com/RAUI-labs/raui/blob/master/media/raui-in-game-material-ui.gif?raw=true)\n\n- [`RAUI Todo App`](https://github.com/RAUI-labs/raui/tree/master/demos/todo-app)\n  An example of TODO app with dark theme Material component library.\n\n  ![RAUI Todo App](https://github.com/RAUI-labs/raui/blob/master/media/raui-todo-app-material-ui.gif?raw=true)\n\n## Contribute\nAny contribution that improves quality of the RAUI toolset is highly appreciated.\n- If you have a feature request, create an Issue post and explain the goal of the feature along with the reason why it is needed and its pros and cons.\n- Whenever you would like to create na PR, please create your feature branch from `next` branch so when it gets approved it can be simply merged using GitHub merge button\n- All changes are staged into `next` branch and new versions are made out of its commits, master is considered stable/release branch.\n- Changes should pass tests, you run tests with: `cargo test --all --features all`.\n- This readme file is generated from the `lib.rs` documentation and can be re-generated by using [`cargo readme`][cargo_readme].\n\n[cargo_readme]: https://github.com/livioribeiro/cargo-readme\n\n## Milestones\nRAUI is still in early development phase, so prepare for these changes until v1.0:\n- [ ] Integrate RAUI into one public open source Rust game.\n- [ ] Write documentation.\n- [ ] Write MD book about how to use RAUI properly and make UI efficient.\n- [ ] Implement VDOM diffing algorithm for tree rebuilding optimizations.\n- [ ] Find a solution (or make it a feature) for moving from trait objects data into strongly typed data for properties and states.\n\nThings that now are done:\n- [x] Add suport for layouting.\n- [x] Add suport for interactions (user input).\n- [x] Create renderer for GGEZ game framework.\n- [x] Create basic user components.\n- [x] Create basic Hello World example application.\n- [x] Decouple shared props from props (don't merge them, put shared props in context).\n- [x] Create TODO app as an example.\n- [x] Create In-Game app as an example.\n- [x] Create renderer for Oxygengine game engine.\n- [x] Add complex navigation system.\n- [x] Create scroll box widget.\n- [x] Add \"immediate mode UI\" builder to give alternative to macros-based declarative mode UI building (with zero overhead, it is an equivalent to declarative macros used by default, immediate mode and declarative mode widgets can talk to each other without a hassle).\n- [x] Add data binding property type to easily mutate data from outside of the application.\n- [x] Create tesselation renderer that produces Vertex + Index + Batch buffers ready for mesh renderers.\n- [x] Move from `widget_component!` and `widget_hook!` macro rules to `pre_hooks` and `post_hooks` function attributes.\n- [x] Add derive `PropsData` and `MessageData` procedural macros to gradually replace the need to call `implement_props_data!` and `implement_message_data!` macros.\n- [x] Add support for portals - an easy way to \"teleport\" sub-tree into another tree node (useful for modals and drag & drop).\n- [x] Add support for View-Model for sharing data between host app and UI."
  },
  {
    "path": "crates/_/Cargo.toml",
    "content": "[package]\nname = \"raui\"\nversion = \"0.70.17\"\nauthors = [\"Patryk 'PsichiX' Budzynski <psichix@gmail.com>\"]\nedition = \"2024\"\ndescription = \"Renderer Agnostic User Interface\"\nreadme = \"../../README.md\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/RAUI-labs/raui\"\nkeywords = [\"renderer\", \"agnostic\", \"ui\", \"interface\", \"gamedev\"]\ncategories = [\"gui\", \"rendering::graphics-api\"]\n\n[features]\nmaterial = [\"raui-material\"]\nretained = [\"raui-retained\"]\nimmediate = [\"raui-immediate\"]\nimmediate-widgets = [\"raui-immediate-widgets\"]\njson = [\"raui-json-renderer\"]\ntesselate = [\"raui-tesselate-renderer\"]\napp = [\"raui-app\"]\nall = [\n  \"material\",\n  \"retained\",\n  \"immediate\",\n  \"immediate-widgets\",\n  \"tesselate\",\n  \"json\",\n  \"app\",\n]\nimport-all = []\n\n[dependencies]\nraui-core = { path = \"../core\", version = \"0.70\" }\n\n[dependencies.raui-material]\npath = \"../material\"\nversion = \"0.70\"\noptional = true\n\n[dependencies.raui-retained]\npath = \"../retained\"\nversion = \"0.70\"\noptional = true\n\n[dependencies.raui-immediate]\npath = \"../immediate\"\nversion = \"0.70\"\noptional = true\n\n[dependencies.raui-immediate-widgets]\npath = \"../immediate-widgets\"\nversion = \"0.70\"\noptional = true\n\n[dependencies.raui-json-renderer]\npath = \"../json-renderer\"\nversion = \"0.70\"\noptional = true\n\n[dependencies.raui-tesselate-renderer]\npath = \"../tesselate-renderer\"\nversion = \"0.70\"\noptional = true\n\n[dependencies.raui-app]\npath = \"../app\"\nversion = \"0.70\"\noptional = true\n\n[dev-dependencies]\nraui-core = { path = \"../core\" }\nraui-material = { path = \"../material\" }\nraui-immediate = { path = \"../immediate\" }\nraui-immediate-widgets = { path = \"../immediate-widgets\" }\nraui-retained = { path = \"../retained\" }\nraui-app = { path = \"../app\" }\nraui-json-renderer = { path = \"../json-renderer\" }\nserde = { version = \"1\", features = [\"derive\"] }\nserde_json = \"1\"\n\n[package.metadata.docs.rs]\nfeatures = [\"all\"]\n"
  },
  {
    "path": "crates/_/build.rs",
    "content": "use std::fs::File;\nuse std::io::Write;\nuse std::path::Path;\n\nfn main() {\n    let mut output = String::new();\n    output.push_str(\"#![allow(ambiguous_glob_reexports)]\\n\");\n    output.push_str(\"#![allow(unused_variables)]\\n\");\n\n    visit_dirs(\n        Path::new(\"../core/src\"),\n        \"raui_core\",\n        None,\n        &mut output,\n        &[],\n    );\n    visit_dirs(\n        Path::new(\"../material/src\"),\n        \"raui_material\",\n        Some(\"material\"),\n        &mut output,\n        &[],\n    );\n    visit_dirs(\n        Path::new(\"../retained/src\"),\n        \"raui_retained\",\n        Some(\"retained\"),\n        &mut output,\n        &[],\n    );\n    visit_dirs(\n        Path::new(\"../immediate/src\"),\n        \"raui_immediate\",\n        Some(\"immediate\"),\n        &mut output,\n        &[],\n    );\n    visit_dirs(\n        Path::new(\"../immediate-widgets/src\"),\n        \"raui_immediate_widgets\",\n        Some(\"immediate-widgets\"),\n        &mut output,\n        &[],\n    );\n    visit_dirs(\n        Path::new(\"../tesselate-renderer/src\"),\n        \"raui_tesselate_renderer\",\n        Some(\"tesselate\"),\n        &mut output,\n        &[],\n    );\n    visit_dirs(\n        Path::new(\"../json-renderer/src\"),\n        \"raui_json_renderer\",\n        Some(\"json\"),\n        &mut output,\n        &[],\n    );\n    visit_dirs(\n        Path::new(\"../app/src\"),\n        \"raui_app\",\n        Some(\"app\"),\n        &mut output,\n        &[\n            \"asset_manager.rs\",\n            \"interactions.rs\",\n            \"text_measurements.rs\",\n        ],\n    );\n\n    let out_path = Path::new(\"src\").join(\"import_all.rs\");\n    let mut file = File::create(&out_path).expect(\"Failed to create import_all.rs\");\n    file.write_all(output.as_bytes()).expect(\"Write failed\");\n}\n\nfn visit_dirs(\n    dir: &Path,\n    prefix: &str,\n    feature: Option<&str>,\n    output: &mut String,\n    ignore: &[&str],\n) {\n    for entry in std::fs::read_dir(dir).unwrap() {\n        let entry = entry.unwrap();\n        let path = entry.path();\n\n        if path.is_dir() {\n            if path.join(\"mod.rs\").exists() {\n                let mod_path = path.strip_prefix(dir).unwrap();\n                let mod_name = mod_path.to_string_lossy().replace(\"/\", \"::\");\n                if let Some(feature) = feature {\n                    output.push_str(&format!(\"#[cfg(feature = \\\"{feature}\\\")]\\n\"));\n                }\n                output.push_str(&format!(\"pub use {prefix}::{mod_name}::*;\\n\"));\n                visit_dirs(\n                    &path,\n                    &format!(\"{prefix}::{mod_name}\"),\n                    feature,\n                    output,\n                    ignore,\n                );\n            }\n        } else if let Some(ext) = path.extension()\n            && ext == \"rs\"\n        {\n            if path.file_name().unwrap() == \"lib.rs\" {\n                if let Some(feature) = feature {\n                    output.push_str(&format!(\"#[cfg(feature = \\\"{feature}\\\")]\\n\"));\n                }\n                output.push_str(&format!(\"pub use {prefix}::*;\\n\"));\n                continue;\n            }\n\n            if path.file_name().unwrap() == \"mod.rs\"\n                || path.file_name().unwrap() == \"import_all.rs\"\n                || ignore.iter().any(|name| path.file_name().unwrap() == *name)\n            {\n                continue;\n            }\n\n            let mod_path = path.strip_prefix(dir).unwrap();\n            let mut mod_name = mod_path.to_string_lossy().replace(\"/\", \"::\");\n            mod_name = mod_name.trim_end_matches(\".rs\").to_string();\n            if let Some(feature) = feature {\n                output.push_str(&format!(\"#[cfg(feature = \\\"{feature}\\\")]\\n\"));\n            }\n            output.push_str(&format!(\"pub use {prefix}::{mod_name}::*;\\n\"));\n        }\n    }\n}\n"
  },
  {
    "path": "crates/_/examples/anchor_box.rs",
    "content": "use raui_app::app::declarative::DeclarativeApp;\nuse raui_core::{\n    make_widget,\n    widget::{\n        WidgetRef,\n        component::{\n            RelativeLayoutProps,\n            containers::{anchor_box::anchor_box, content_box::content_box},\n            image_box::{ImageBoxProps, image_box},\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::content::ContentBoxItemLayout,\n        utils::Color,\n    },\n};\n\nfn preview(ctx: WidgetContext) -> WidgetNode {\n    // we print this widget props to show how AnchorProps values change relative to window resize.\n    println!(\"Preview props: {:#?}\", ctx.props);\n\n    // we create simple colored image that fills available space just to make you see the values.\n    make_widget!(image_box)\n        .with_props(ImageBoxProps::colored(Color {\n            r: 1.0,\n            g: 0.25,\n            b: 0.25,\n            a: 1.0,\n        }))\n        .into()\n}\n\nfn main() {\n    // we create widget reference first so we can apply it to some widget and and reference\n    //that widget in another place - basically what widget reference is, it is a way to read\n    // some other widget ID in some other place outside the referenced widget scope.\n    let idref = WidgetRef::default();\n\n    let tree = make_widget!(content_box)\n        // we apply widget reference to the root content box so we can reference that root widget\n        // later in anchor box to enable it to calculate how anchor box content is lay out relative\n        // to the root widget - this is the most important thing to setup, because if we won't do\n        // that, anchor box would not be able to give its content a proper data about its layout\n        // relative to the referenced widget. Note that, you can reference ANY widget in the widget\n        // tree - it will always give you a relative location to any widget you provide.\n        .idref(idref.clone())\n        .listed_slot(\n            make_widget!(anchor_box)\n                // we pass widget reference to anchor box via RelativeLayoutProps, because anchor\n                // uses relative layout hook to perform calculations of relative layout box.\n                .with_props(RelativeLayoutProps {\n                    relative_to: idref.into(),\n                })\n                // we apply margin to anchor box just to make it not fill entire space by default.\n                .with_props(ContentBoxItemLayout {\n                    margin: 100.0.into(),\n                    ..Default::default()\n                })\n                .named_slot(\"content\", make_widget!(preview)),\n        );\n\n    DeclarativeApp::simple(\"Anchor Box\", tree);\n}\n"
  },
  {
    "path": "crates/_/examples/app.rs",
    "content": "use raui_app::app::declarative::DeclarativeApp;\nuse raui_core::{\n    make_widget,\n    widget::{\n        component::{\n            containers::{flex_box::FlexBoxProps, vertical_box::vertical_box},\n            image_box::{ImageBoxProps, image_box},\n            text_box::{TextBoxProps, text_box},\n        },\n        unit::{\n            flex::FlexBoxItemLayout,\n            text::{TextBoxFont, TextBoxHorizontalAlign, TextBoxSizeValue},\n        },\n        utils::Color,\n    },\n};\n\nfn main() {\n    let tree = make_widget!(vertical_box)\n        .with_props(FlexBoxProps {\n            separation: 50.0,\n            ..Default::default()\n        })\n        .listed_slot(\n            make_widget!(image_box).with_props(ImageBoxProps::image_aspect_ratio(\n                \"./demos/hello-world/resources/cats.jpg\",\n                false,\n            )),\n        )\n        .listed_slot(\n            make_widget!(text_box)\n                .with_props(FlexBoxItemLayout::no_growing_and_shrinking())\n                .with_props(TextBoxProps {\n                    text: \"RAUI application example\".to_owned(),\n                    font: TextBoxFont {\n                        name: \"./demos/hello-world/resources/verdana.ttf\".to_owned(),\n                        size: 64.0,\n                    },\n                    color: Color {\n                        r: 0.0,\n                        g: 0.0,\n                        b: 0.5,\n                        a: 1.0,\n                    },\n                    horizontal_align: TextBoxHorizontalAlign::Center,\n                    height: TextBoxSizeValue::Content,\n                    ..Default::default()\n                }),\n        );\n\n    DeclarativeApp::simple(\"RAUI application example\", tree);\n}\n"
  },
  {
    "path": "crates/_/examples/button_external.rs",
    "content": "// Make sure you have seen `button_internal` code example first, because this is an evolution of that.\n\nuse raui_app::app::declarative::DeclarativeApp;\nuse raui_core::{\n    make_widget, pre_hooks,\n    widget::{\n        component::{\n            image_box::{ImageBoxProps, image_box},\n            interactive::{\n                button::{ButtonNotifyMessage, ButtonNotifyProps, button},\n                navigation::{NavItemActive, use_nav_container_active},\n            },\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::image::{ImageBoxColor, ImageBoxMaterial, ImageBoxSizeValue},\n        utils::Color,\n    },\n};\n\n// we create app hook that just receives button state change messages and prints them.\nfn use_app(ctx: &mut WidgetContext) {\n    ctx.life_cycle.change(|ctx| {\n        for msg in ctx.messenger.messages {\n            if let Some(msg) = msg.as_any().downcast_ref::<ButtonNotifyMessage>() {\n                println!(\"Button message: {msg:#?}\");\n            }\n        }\n    });\n}\n\n#[pre_hooks(use_nav_container_active, use_app)]\nfn app(mut ctx: WidgetContext) -> WidgetNode {\n    make_widget!(button)\n        .with_props(NavItemActive)\n        // we tell button to notify this component (send messages to it) whenever button state changes.\n        .with_props(ButtonNotifyProps(ctx.id.to_owned().into()))\n        .named_slot(\n            \"content\",\n            make_widget!(image_box).with_props(ImageBoxProps {\n                material: ImageBoxMaterial::Color(ImageBoxColor {\n                    color: Color {\n                        r: 1.0,\n                        g: 0.25,\n                        b: 0.25,\n                        a: 1.0,\n                    },\n                    ..Default::default()\n                }),\n                width: ImageBoxSizeValue::Exact(400.0),\n                height: ImageBoxSizeValue::Exact(300.0),\n                ..Default::default()\n            }),\n        )\n        .into()\n}\n\nfn main() {\n    DeclarativeApp::simple(\"Button - Sending state to other widget\", make_widget!(app));\n}\n"
  },
  {
    "path": "crates/_/examples/button_internal.rs",
    "content": "use raui_app::app::declarative::DeclarativeApp;\nuse raui_core::{\n    make_widget, pre_hooks,\n    widget::{\n        component::{\n            image_box::{ImageBoxProps, image_box},\n            interactive::{\n                button::{ButtonProps, button},\n                navigation::{NavItemActive, use_nav_container_active},\n            },\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::image::{ImageBoxColor, ImageBoxMaterial, ImageBoxSizeValue},\n        utils::Color,\n    },\n};\n\n// mark the root widget as navigable container to allow button to subscribe to navigation system.\n#[pre_hooks(use_nav_container_active)]\nfn app(mut ctx: WidgetContext) -> WidgetNode {\n    // button is the simplest and the most common in use navigable item that can react to user input.\n    make_widget!(button)\n        // enable button navigation (it is disabled by default).\n        .with_props(NavItemActive)\n        // by default button state of the button is passed to the content widget with\n        // `ButtonProps` props data, so content widget can read it and change its appearance.\n        .named_slot(\"content\", make_widget!(internal))\n        .into()\n}\n\nfn internal(ctx: WidgetContext) -> WidgetNode {\n    // first we unpack button state from button props.\n    let ButtonProps {\n        // selected state means, well..widget has got selected. selection in navigation is more\n        // complex than that and it deserves separate deeper explanation, but in essence: whenever\n        // user navigate over the UI, RAUI performs selection on navigable items, navigable items\n        // may be nested and whenever some widget gets selected, all of its navigable parents\n        // receive selection event too, so there is not only one widget that might be selected at\n        // a time, but there might be a chain of selected items, as long as they are on the way\n        // toward actually selected navigable item in the widget tree.\n        selected,\n        // trigger state means navigable item got Accept event, which in context of the button\n        // means: button is selected and user performed \"left mouse button click\".\n        trigger,\n        // context state is similar to trigger state, in this case it means user performed \"right\n        // mouse button click\".\n        context,\n        ..\n    } = ctx.props.read_cloned_or_default();\n\n    let color = if trigger {\n        Color {\n            r: 1.0,\n            g: 0.25,\n            b: 0.25,\n            a: 1.0,\n        }\n    } else if context {\n        Color {\n            r: 0.25,\n            g: 1.0,\n            b: 0.25,\n            a: 1.0,\n        }\n    } else if selected {\n        Color {\n            r: 0.25,\n            g: 0.25,\n            b: 1.0,\n            a: 1.0,\n        }\n    } else {\n        Color {\n            r: 0.25,\n            g: 0.25,\n            b: 0.25,\n            a: 1.0,\n        }\n    };\n\n    make_widget!(image_box)\n        .with_props(ImageBoxProps {\n            material: ImageBoxMaterial::Color(ImageBoxColor {\n                color,\n                ..Default::default()\n            }),\n            width: ImageBoxSizeValue::Exact(400.0),\n            height: ImageBoxSizeValue::Exact(300.0),\n            ..Default::default()\n        })\n        .into()\n}\n\nfn main() {\n    DeclarativeApp::simple(\"Button - Pass state to its child\", make_widget!(app));\n}\n"
  },
  {
    "path": "crates/_/examples/canvas.rs",
    "content": "// Make sure you have seen `render_workers` code example first, because this is an evolution of that.\n\nuse raui_app::{\n    Vertex,\n    app::declarative::DeclarativeApp,\n    components::canvas::{CanvasProps, DrawOnCanvasMessage, RequestCanvasRedrawMessage, canvas},\n    render_worker::RenderWorkerTaskContext,\n    third_party::spitfire_glow::{\n        graphics::GraphicsBatch,\n        renderer::{GlowBlending, GlowUniformValue},\n    },\n};\nuse raui_core::{\n    make_widget, pre_hooks,\n    widget::{context::WidgetContext, node::WidgetNode, utils::Color},\n};\n\nfn use_my_canvas(ctx: &mut WidgetContext) {\n    ctx.life_cycle.change(|ctx| {\n        // canvas will send redraw request on mount and resize.\n        // we can react with sending drawing task message to canvas.\n        for msg in ctx.messenger.messages {\n            if msg\n                .as_any()\n                .downcast_ref::<RequestCanvasRedrawMessage>()\n                .is_some()\n            {\n                ctx.messenger.write(\n                    ctx.id.to_owned(),\n                    DrawOnCanvasMessage::function(render_task),\n                );\n            }\n        }\n    });\n}\n\n#[pre_hooks(use_my_canvas)]\nfn my_canvas(mut ctx: WidgetContext) -> WidgetNode {\n    // we are specializing canvas widget by simply executing canvas\n    // widget function in place, so we have easier times sending\n    // drawing task to canvas by its id.\n    canvas(ctx)\n}\n\nfn main() {\n    let tree = make_widget!(my_canvas).with_props(CanvasProps {\n        color: Color {\n            r: 0.0,\n            g: 0.0,\n            b: 0.0,\n            a: 0.5,\n        },\n        clear: true,\n    });\n\n    DeclarativeApp::simple(\"Canvas\", tree);\n}\n\nfn render_task(ctx: RenderWorkerTaskContext) {\n    ctx.graphics.state.stream.batch_optimized(GraphicsBatch {\n        shader: Some(ctx.colored_shader.clone()),\n        uniforms: [(\n            \"u_projection_view\".into(),\n            GlowUniformValue::M4(\n                ctx.graphics\n                    .state\n                    .main_camera\n                    .world_matrix()\n                    .into_col_array(),\n            ),\n        )]\n        .into_iter()\n        .collect(),\n        textures: Default::default(),\n        blending: GlowBlending::Alpha,\n        scissor: None,\n        wireframe: false,\n    });\n    ctx.graphics.state.stream.quad([\n        Vertex {\n            position: [50.0, 50.0],\n            uv: [0.0, 0.0, 0.0],\n            color: [1.0, 0.0, 0.0, 1.0],\n        },\n        Vertex {\n            position: [ctx.graphics.state.main_camera.screen_size.x - 50.0, 50.0],\n            uv: [0.0, 0.0, 0.0],\n            color: [0.0, 1.0, 0.0, 1.0],\n        },\n        Vertex {\n            position: [\n                ctx.graphics.state.main_camera.screen_size.x - 50.0,\n                ctx.graphics.state.main_camera.screen_size.y - 50.0,\n            ],\n            uv: [0.0, 0.0, 0.0],\n            color: [0.0, 0.0, 1.0, 1.0],\n        },\n        Vertex {\n            position: [50.0, ctx.graphics.state.main_camera.screen_size.y - 50.0],\n            uv: [0.0, 0.0, 0.0],\n            color: [1.0, 1.0, 0.0, 1.0],\n        },\n    ]);\n}\n"
  },
  {
    "path": "crates/_/examples/content_box.rs",
    "content": "use raui_app::app::declarative::DeclarativeApp;\nuse raui_core::{\n    make_widget,\n    widget::{\n        component::{\n            containers::content_box::content_box,\n            image_box::{ImageBoxProps, image_box},\n        },\n        unit::content::ContentBoxItemLayout,\n        utils::{Color, Rect},\n    },\n};\n\nfn main() {\n    let tree = make_widget!(content_box)\n        .listed_slot(\n            make_widget!(image_box)\n                .with_props(ImageBoxProps::colored(Color {\n                    r: 1.0,\n                    g: 0.25,\n                    b: 0.25,\n                    a: 1.0,\n                }))\n                .with_props(ContentBoxItemLayout {\n                    anchors: Rect {\n                        left: -1.0,\n                        right: 2.0,\n                        top: -1.0,\n                        bottom: 2.0,\n                    },\n                    keep_in_bounds: true.into(),\n                    ..Default::default()\n                }),\n        )\n        .listed_slot(\n            make_widget!(image_box)\n                .with_props(ImageBoxProps::colored(Color {\n                    r: 0.25,\n                    g: 1.0,\n                    b: 0.25,\n                    a: 1.0,\n                }))\n                .with_props(ContentBoxItemLayout {\n                    margin: 64.0.into(),\n                    ..Default::default()\n                }),\n        )\n        .listed_slot(\n            make_widget!(image_box)\n                .with_props(ImageBoxProps::colored(Color {\n                    r: 0.25,\n                    g: 0.25,\n                    b: 1.0,\n                    a: 1.0,\n                }))\n                .with_props(ContentBoxItemLayout {\n                    anchors: Rect {\n                        left: 0.5,\n                        right: 0.75,\n                        top: 0.25,\n                        bottom: 0.75,\n                    },\n                    ..Default::default()\n                }),\n        );\n\n    DeclarativeApp::simple(\"Content Box\", tree);\n}\n"
  },
  {
    "path": "crates/_/examples/context_box.rs",
    "content": "// Make sure you have seen `portal_box` code example first, because this is an evolution of that.\n\nuse raui_app::{\n    app::{App, AppConfig, declarative::DeclarativeApp},\n    event::{ElementState, Event, VirtualKeyCode, WindowEvent},\n};\nuse raui_core::{\n    make_widget, pre_hooks,\n    view_model::ViewModel,\n    widget::{\n        WidgetRef,\n        component::{\n            containers::{\n                anchor_box::PivotBoxProps,\n                content_box::content_box,\n                context_box::{ContextBoxProps, portals_context_box},\n                horizontal_box::{HorizontalBoxProps, horizontal_box},\n                portal_box::PortalsContainer,\n            },\n            image_box::{ImageBoxProps, image_box},\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::{\n            flex::FlexBoxItemLayout,\n            image::{ImageBoxColor, ImageBoxMaterial, ImageBoxSizeValue},\n        },\n        utils::Color,\n    },\n};\n\nconst DATA: &str = \"data\";\n\nfn use_app(ctx: &mut WidgetContext) {\n    ctx.life_cycle.mount(|mut ctx| {\n        ctx.view_models\n            .bindings(DATA, \"\")\n            .unwrap()\n            .bind(ctx.id.to_owned());\n    });\n}\n\n#[pre_hooks(use_app)]\nfn app(mut ctx: WidgetContext) -> WidgetNode {\n    let idref = WidgetRef::default();\n    // we read value from view model created with app builder.\n    let data = ctx\n        .view_models\n        .view_model(DATA)\n        .unwrap()\n        .read::<(bool, bool, bool)>()\n        .unwrap();\n\n    make_widget!(content_box)\n        .idref(idref.clone())\n        .with_shared_props(PortalsContainer(idref))\n        .listed_slot(\n            make_widget!(horizontal_box)\n                .with_props(HorizontalBoxProps {\n                    separation: 25.0,\n                    ..Default::default()\n                })\n                .listed_slot(\n                    make_widget!(icon)\n                        // clear this flex box item layout (no growing, shrinking or filling).\n                        .with_props(FlexBoxItemLayout::cleared())\n                        // pass context box state read from app data.\n                        .with_props(data.0)\n                        // set icon color.\n                        .with_props(Color {\n                            r: 1.0,\n                            g: 0.25,\n                            b: 0.25,\n                            a: 1.0,\n                        })\n                        // tell context widget how to position it relative to the content widget.\n                        .with_props(PivotBoxProps {\n                            pivot: 0.0.into(),\n                            align: 0.0.into(),\n                        }),\n                )\n                .listed_slot(\n                    make_widget!(icon)\n                        .with_props(FlexBoxItemLayout::cleared())\n                        .with_props(data.1)\n                        .with_props(Color {\n                            r: 0.25,\n                            g: 1.0,\n                            b: 0.25,\n                            a: 1.0,\n                        })\n                        .with_props(PivotBoxProps {\n                            pivot: 0.5.into(),\n                            align: 0.5.into(),\n                        }),\n                )\n                .listed_slot(\n                    make_widget!(icon)\n                        .with_props(FlexBoxItemLayout::cleared())\n                        .with_props(data.2)\n                        .with_props(Color {\n                            r: 0.25,\n                            g: 0.25,\n                            b: 1.0,\n                            a: 1.0,\n                        })\n                        .with_props(PivotBoxProps {\n                            pivot: 1.0.into(),\n                            align: 1.0.into(),\n                        }),\n                ),\n        )\n        .into()\n}\n\n// custom icon component composed out of icon image as its content and context image that we show\n// when bool props value is true.\nfn icon(ctx: WidgetContext) -> WidgetNode {\n    // we use `portals_context_box` to allow this context box properly calculate context widget\n    // relative to the portals container.\n    make_widget!(portals_context_box)\n        // pass pivot props to context box,\n        .with_props(ctx.props.read_cloned_or_default::<PivotBoxProps>())\n        .with_props(ContextBoxProps {\n            // read bool props value and use it to tell if context widget is gonna be shown.\n            show: ctx.props.read_cloned_or_default::<bool>(),\n        })\n        // put colored image box as content widget.\n        .named_slot(\n            \"content\",\n            make_widget!(image_box).with_props(ImageBoxProps {\n                material: ImageBoxMaterial::Color(ImageBoxColor {\n                    color: ctx.props.read_cloned_or_default::<Color>(),\n                    ..Default::default()\n                }),\n                width: ImageBoxSizeValue::Exact(100.0),\n                height: ImageBoxSizeValue::Exact(100.0),\n                ..Default::default()\n            }),\n        )\n        // put gray image box as context widget.\n        .named_slot(\n            \"context\",\n            make_widget!(image_box).with_props(ImageBoxProps {\n                material: ImageBoxMaterial::Color(ImageBoxColor {\n                    color: Color {\n                        r: 0.25,\n                        g: 0.25,\n                        b: 0.25,\n                        a: 1.0,\n                    },\n                    ..Default::default()\n                }),\n                width: ImageBoxSizeValue::Exact(150.0),\n                height: ImageBoxSizeValue::Exact(50.0),\n                ..Default::default()\n            }),\n        )\n        .into()\n}\n\nfn main() {\n    let app = DeclarativeApp::default()\n        .tree(make_widget!(app))\n        // we use tuple of 3 bools that will represent state of individual context box.\n        .view_model(DATA, ViewModel::new_object((false, true, false)))\n        .event(move |application, event, _, _| {\n            let mut data = application\n                .view_models\n                .get_mut(DATA)\n                .unwrap()\n                .write_notified::<(bool, bool, bool)>()\n                .unwrap();\n\n            if let Event::WindowEvent {\n                event: WindowEvent::KeyboardInput { input, .. },\n                ..\n            } = event\n                && input.state == ElementState::Pressed\n                && let Some(key) = input.virtual_keycode\n            {\n                match key {\n                    VirtualKeyCode::Key1 | VirtualKeyCode::Numpad1 => {\n                        // change state of given context box in app data.\n                        data.0 = !data.0;\n                    }\n                    VirtualKeyCode::Key2 | VirtualKeyCode::Numpad2 => {\n                        data.1 = !data.1;\n                    }\n                    VirtualKeyCode::Key3 | VirtualKeyCode::Numpad3 => {\n                        data.2 = !data.2;\n                    }\n                    _ => {}\n                }\n            }\n            true\n        });\n\n    App::new(AppConfig::default().title(\"Context Box\")).run(app);\n}\n"
  },
  {
    "path": "crates/_/examples/flex_box.rs",
    "content": "use raui_app::app::declarative::DeclarativeApp;\nuse raui_core::{\n    make_widget,\n    widget::{\n        component::{\n            containers::flex_box::{FlexBoxProps, flex_box},\n            image_box::{ImageBoxProps, image_box},\n        },\n        unit::flex::{FlexBoxDirection, FlexBoxItemLayout},\n        utils::Color,\n    },\n};\n\nfn main() {\n    let tree = make_widget!(flex_box)\n        .with_props(FlexBoxProps {\n            direction: FlexBoxDirection::VerticalBottomToTop,\n            ..Default::default()\n        })\n        .listed_slot(\n            make_widget!(image_box)\n                .with_props(ImageBoxProps::colored(Color {\n                    r: 1.0,\n                    g: 0.25,\n                    b: 0.25,\n                    a: 1.0,\n                }))\n                .with_props(FlexBoxItemLayout {\n                    // basis sets exact size of the item in main axis.\n                    basis: Some(100.0),\n                    // weight of the item when its layout box has to grow.\n                    grow: 0.5,\n                    // weight of the item when its layout box has to shrink (0.0 means no shrinking).\n                    shrink: 0.0,\n                    // percentage of the item size in cross axis (here how much of horizontal space it fills).\n                    fill: 0.75,\n                    // tells how much to which side item is aligned when there is free space available.\n                    align: 1.0,\n                    ..Default::default()\n                }),\n        )\n        .listed_slot(\n            make_widget!(image_box)\n                .with_props(ImageBoxProps::colored(Color {\n                    r: 0.25,\n                    g: 1.0,\n                    b: 0.25,\n                    a: 1.0,\n                }))\n                .with_props(FlexBoxItemLayout {\n                    margin: 10.0.into(),\n                    ..Default::default()\n                }),\n        )\n        .listed_slot(\n            make_widget!(image_box)\n                .with_props(ImageBoxProps::colored(Color {\n                    r: 0.25,\n                    g: 0.25,\n                    b: 1.0,\n                    a: 1.0,\n                }))\n                .with_props(FlexBoxItemLayout {\n                    basis: Some(100.0),\n                    grow: 0.0,\n                    shrink: 0.5,\n                    fill: 0.5,\n                    align: 0.5,\n                    ..Default::default()\n                }),\n        );\n\n    DeclarativeApp::simple(\"Flex Box\", tree);\n}\n"
  },
  {
    "path": "crates/_/examples/flex_box_content_size.rs",
    "content": "use raui_app::app::declarative::DeclarativeApp;\nuse raui_core::{\n    make_widget,\n    widget::{\n        component::{\n            containers::{\n                flex_box::{FlexBoxProps, flex_box},\n                size_box::{SizeBoxProps, size_box},\n            },\n            image_box::{ImageBoxProps, image_box},\n            text_box::{TextBoxProps, text_box},\n        },\n        unit::{\n            flex::{FlexBoxDirection, FlexBoxItemLayout},\n            image::{ImageBoxColor, ImageBoxMaterial, ImageBoxSizeValue},\n            size::SizeBoxSizeValue,\n            text::{TextBoxFont, TextBoxSizeValue},\n        },\n        utils::Color,\n    },\n};\n\nfn main() {\n    let tree = make_widget!(size_box)\n        .with_props(SizeBoxProps {\n            width: SizeBoxSizeValue::Fill,\n            height: SizeBoxSizeValue::Content,\n            ..Default::default()\n        })\n        .named_slot(\n            \"content\",\n            make_widget!(flex_box)\n                .with_props(FlexBoxProps {\n                    direction: FlexBoxDirection::VerticalTopToBottom,\n                    ..Default::default()\n                })\n                .listed_slot(\n                    make_widget!(text_box)\n                        .with_props(FlexBoxItemLayout::no_growing_and_shrinking())\n                        .with_props(TextBoxProps {\n                            text: \"Hello\\nWorld!\".to_owned(),\n                            font: TextBoxFont {\n                                name: \"./demos/hello-world/resources/verdana.ttf\".to_owned(),\n                                size: 64.0,\n                            },\n                            color: Color {\n                                r: 0.0,\n                                g: 0.0,\n                                b: 0.0,\n                                a: 1.0,\n                            },\n                            height: TextBoxSizeValue::Content,\n                            ..Default::default()\n                        }),\n                )\n                .listed_slot(\n                    make_widget!(image_box)\n                        .with_props(FlexBoxItemLayout::no_growing_and_shrinking())\n                        .with_props(ImageBoxProps {\n                            height: ImageBoxSizeValue::Exact(100.0),\n                            material: ImageBoxMaterial::Color(ImageBoxColor {\n                                color: Color {\n                                    r: 1.0,\n                                    g: 0.5,\n                                    b: 0.0,\n                                    a: 1.0,\n                                },\n                                ..Default::default()\n                            }),\n                            ..Default::default()\n                        }),\n                )\n                // this image should not be visible at all, a zero size layout.\n                .listed_slot(\n                    make_widget!(image_box).with_props(ImageBoxProps::colored(Color {\n                        r: 0.5,\n                        g: 0.5,\n                        b: 0.5,\n                        a: 1.0,\n                    })),\n                ),\n        );\n\n    DeclarativeApp::simple(\"Flex Box - Adaptive content size\", tree);\n}\n"
  },
  {
    "path": "crates/_/examples/flex_box_wrapping.rs",
    "content": "use raui_app::app::declarative::DeclarativeApp;\nuse raui_core::{\n    make_widget,\n    widget::{\n        component::{\n            containers::{\n                flex_box::{FlexBoxProps, flex_box},\n                size_box::{SizeBoxProps, size_box},\n            },\n            image_box::{ImageBoxProps, image_box},\n        },\n        unit::{\n            flex::{FlexBoxDirection, FlexBoxItemLayout},\n            size::SizeBoxSizeValue,\n        },\n        utils::Color,\n    },\n};\n\nfn main() {\n    let tree = make_widget!(flex_box)\n        .with_props(FlexBoxProps {\n            direction: FlexBoxDirection::VerticalTopToBottom,\n            // Wrapping makes children fit into multiple rows/columns.\n            wrap: true,\n            ..Default::default()\n        })\n        .listed_slots((0..18).map(|_| {\n            make_widget!(size_box)\n                .with_props(FlexBoxItemLayout::cleared())\n                .with_props(SizeBoxProps {\n                    width: SizeBoxSizeValue::Exact(100.0),\n                    height: SizeBoxSizeValue::Exact(100.0),\n                    margin: 20.0.into(),\n                    ..Default::default()\n                })\n                .named_slot(\n                    \"content\",\n                    make_widget!(image_box).with_props(ImageBoxProps::colored(Color {\n                        r: 0.25,\n                        g: 0.25,\n                        b: 0.25,\n                        a: 1.0,\n                    })),\n                )\n        }));\n\n    DeclarativeApp::simple(\"Flex Box - Wrapping content\", tree);\n}\n"
  },
  {
    "path": "crates/_/examples/float_view.rs",
    "content": "use raui_app::app::{App, AppConfig, declarative::DeclarativeApp};\nuse raui_core::{\n    make_widget, pre_hooks,\n    view_model::{ViewModel, ViewModelValue},\n    widget::{\n        component::{\n            containers::float_box::{\n                FloatBoxChange, FloatBoxChangeMessage, FloatBoxNotifyProps, FloatBoxProps,\n                FloatBoxState, float_box,\n            },\n            image_box::{ImageBoxProps, image_box},\n            interactive::{\n                float_view::float_view_control,\n                navigation::{NavItemActive, use_nav_container_active},\n            },\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::{\n            content::ContentBoxItemLayout,\n            image::{ImageBoxColor, ImageBoxMaterial, ImageBoxSizeValue},\n        },\n        utils::{Color, Rect, Vec2},\n    },\n};\n\nconst DATA: &str = \"data\";\nconst PANELS: &str = \"panels\";\n\n// AppData holds list of floating panels positions and their color.\nstruct AppData {\n    panels: ViewModelValue<Vec<(Vec2, Color)>>,\n}\n\nfn use_app(ctx: &mut WidgetContext) {\n    ctx.life_cycle.mount(|mut ctx| {\n        ctx.view_models\n            .bindings(DATA, PANELS)\n            .unwrap()\n            .bind(ctx.id.to_owned());\n    });\n\n    ctx.life_cycle.unmount(|mut ctx| {\n        ctx.view_models\n            .bindings(DATA, PANELS)\n            .unwrap()\n            .unbind(ctx.id);\n    });\n\n    ctx.life_cycle.change(|mut ctx| {\n        let mut view_model = ctx\n            .view_models\n            .view_model_mut(DATA)\n            .unwrap()\n            .write::<AppData>()\n            .unwrap();\n\n        for msg in ctx.messenger.messages {\n            // We listen for float box change messages sent from `float_view_control`\n            // widgets and move sender panel by delta of change.\n            if let Some(msg) = msg.as_any().downcast_ref::<FloatBoxChangeMessage>()\n                && let Ok(index) = msg.sender.key().parse::<usize>()\n                && let FloatBoxChange::RelativePosition(delta) = msg.change\n                && let Some((position, _)) = view_model.panels.get_mut(index)\n            {\n                position.x += delta.x;\n                position.y += delta.y;\n            }\n        }\n    });\n}\n\n#[pre_hooks(use_nav_container_active, use_app)]\nfn app(mut ctx: WidgetContext) -> WidgetNode {\n    let view_model = ctx\n        .view_models\n        .view_model(DATA)\n        .unwrap()\n        .read::<AppData>()\n        .unwrap();\n\n    make_widget!(float_box)\n        .with_props(FloatBoxProps {\n            bounds_left: Some(-300.0),\n            bounds_right: Some(600.0),\n            bounds_top: Some(-300.0),\n            bounds_bottom: Some(400.0),\n        })\n        .with_props(FloatBoxState {\n            position: Vec2 { x: 0.0, y: 0.0 },\n            zoom: 2.0,\n        })\n        .listed_slot(\n            // `float_view_control` widget reacts to dragging action and sends\n            // that dragging movement delta to widget that wants to be notified.\n            // In this case, we want to notify `float_box` widget so it will\n            // reposition its content panels.\n            make_widget!(float_view_control)\n                .key(\"panning\")\n                .with_props(NavItemActive)\n                // we make sure panning control fills entire area and stays\n                // in its bounds no matter how content gets repositioned.\n                .with_props(ContentBoxItemLayout {\n                    anchors: Rect {\n                        left: 0.0,\n                        top: 0.0,\n                        right: 1.0,\n                        bottom: 1.0,\n                    },\n                    keep_in_bounds: true.into(),\n                    ..Default::default()\n                })\n                .named_slot(\n                    \"content\",\n                    make_widget!(image_box).with_props(ImageBoxProps::colored(Color {\n                        r: 0.3,\n                        g: 0.3,\n                        b: 0.3,\n                        a: 1.0,\n                    })),\n                ),\n        )\n        .listed_slots(\n            view_model\n                .panels\n                .iter()\n                .enumerate()\n                .map(|(index, (position, color))| {\n                    // we also use `float_view_control` widget for panels so\n                    // they can be dragged around float box.\n                    make_widget!(float_view_control)\n                        .key(index)\n                        .with_props(NavItemActive)\n                        .with_props(FloatBoxNotifyProps(ctx.id.to_owned().into()))\n                        .with_props(ContentBoxItemLayout {\n                            offset: *position,\n                            ..Default::default()\n                        })\n                        .named_slot(\n                            \"content\",\n                            make_widget!(image_box).with_props(ImageBoxProps {\n                                width: ImageBoxSizeValue::Exact(200.0),\n                                height: ImageBoxSizeValue::Exact(150.0),\n                                material: ImageBoxMaterial::Color(ImageBoxColor {\n                                    color: *color,\n                                    ..Default::default()\n                                }),\n                                ..Default::default()\n                            }),\n                        )\n                }),\n        )\n        .into()\n}\n\nfn main() {\n    let panels = vec![\n        (\n            Vec2 { x: 0.0, y: 0.0 },\n            Color {\n                r: 1.0,\n                g: 0.5,\n                b: 0.5,\n                a: 1.0,\n            },\n        ),\n        (\n            Vec2 { x: 100.0, y: 100.0 },\n            Color {\n                r: 0.5,\n                g: 1.0,\n                b: 0.5,\n                a: 1.0,\n            },\n        ),\n        (\n            Vec2 { x: 200.0, y: 200.0 },\n            Color {\n                r: 0.5,\n                g: 0.5,\n                b: 1.0,\n                a: 1.0,\n            },\n        ),\n    ];\n\n    let app = DeclarativeApp::default()\n        .tree(make_widget!(app))\n        .view_model(\n            DATA,\n            ViewModel::produce(|properties| AppData {\n                panels: ViewModelValue::new(panels, properties.notifier(PANELS)),\n            }),\n        );\n\n    App::new(AppConfig::default().title(\"Float View\")).run(app);\n}\n"
  },
  {
    "path": "crates/_/examples/grid_box.rs",
    "content": "use raui_app::app::declarative::DeclarativeApp;\nuse raui_core::{\n    make_widget,\n    widget::{\n        component::{\n            containers::grid_box::{GridBoxProps, grid_box},\n            image_box::{ImageBoxProps, image_box},\n        },\n        unit::grid::GridBoxItemLayout,\n        utils::{Color, IntRect},\n    },\n};\n\nfn main() {\n    let tree = make_widget!(grid_box)\n        .with_props(GridBoxProps {\n            cols: 2,\n            rows: 2,\n            ..Default::default()\n        })\n        .listed_slot(\n            make_widget!(image_box)\n                .with_props(ImageBoxProps::colored(Color {\n                    r: 1.0,\n                    g: 0.25,\n                    b: 0.25,\n                    a: 1.0,\n                }))\n                .with_props(GridBoxItemLayout {\n                    space_occupancy: IntRect {\n                        left: 0,\n                        right: 1,\n                        top: 0,\n                        bottom: 1,\n                    },\n                    ..Default::default()\n                }),\n        )\n        .listed_slot(\n            make_widget!(image_box)\n                .with_props(ImageBoxProps::colored(Color {\n                    r: 0.25,\n                    g: 1.0,\n                    b: 0.25,\n                    a: 1.0,\n                }))\n                .with_props(GridBoxItemLayout {\n                    space_occupancy: IntRect {\n                        left: 1,\n                        right: 2,\n                        top: 0,\n                        bottom: 1,\n                    },\n                    ..Default::default()\n                }),\n        )\n        .listed_slot(\n            make_widget!(image_box)\n                .with_props(ImageBoxProps::colored(Color {\n                    r: 0.25,\n                    g: 0.25,\n                    b: 1.0,\n                    a: 1.0,\n                }))\n                .with_props(GridBoxItemLayout {\n                    space_occupancy: IntRect {\n                        left: 0,\n                        right: 2,\n                        top: 1,\n                        bottom: 2,\n                    },\n                    ..Default::default()\n                }),\n        );\n\n    DeclarativeApp::simple(\"Grid Box\", tree);\n}\n"
  },
  {
    "path": "crates/_/examples/horizontal_box.rs",
    "content": "use raui_app::app::declarative::DeclarativeApp;\nuse raui_core::{\n    make_widget,\n    widget::{\n        component::{\n            containers::horizontal_box::{HorizontalBoxProps, horizontal_box},\n            image_box::{ImageBoxProps, image_box},\n        },\n        unit::flex::FlexBoxItemLayout,\n        utils::Color,\n    },\n};\n\nfn main() {\n    let tree = make_widget!(horizontal_box)\n        .with_props(HorizontalBoxProps {\n            separation: 50.0,\n            ..Default::default()\n        })\n        .listed_slot(\n            make_widget!(image_box)\n                .with_props(ImageBoxProps::colored(Color {\n                    r: 1.0,\n                    g: 0.25,\n                    b: 0.25,\n                    a: 1.0,\n                }))\n                .with_props(FlexBoxItemLayout {\n                    // basis sets exact width of the item.\n                    basis: Some(100.0),\n                    // weight of the item when its layout box has to grow in width.\n                    grow: 0.5,\n                    // weight of the item when its layout box has to shrink in width (0.0 means no shrinking).\n                    shrink: 0.0,\n                    ..Default::default()\n                }),\n        )\n        .listed_slot(\n            make_widget!(image_box).with_props(ImageBoxProps::colored(Color {\n                r: 0.25,\n                g: 1.0,\n                b: 0.25,\n                a: 1.0,\n            })),\n        )\n        .listed_slot(\n            make_widget!(image_box)\n                .with_props(ImageBoxProps::colored(Color {\n                    r: 0.25,\n                    g: 0.25,\n                    b: 1.0,\n                    a: 1.0,\n                }))\n                .with_props(FlexBoxItemLayout {\n                    basis: Some(100.0),\n                    grow: 0.0,\n                    shrink: 0.5,\n                    ..Default::default()\n                }),\n        );\n\n    DeclarativeApp::simple(\"Horizontal Box\", tree);\n}\n"
  },
  {
    "path": "crates/_/examples/image_box_color.rs",
    "content": "use raui_app::app::declarative::DeclarativeApp;\nuse raui_core::{\n    make_widget,\n    widget::{\n        component::image_box::{ImageBoxProps, image_box},\n        unit::image::{ImageBoxColor, ImageBoxMaterial},\n        utils::Color,\n    },\n};\n\nfn main() {\n    let tree = make_widget!(image_box).with_props(ImageBoxProps {\n        material: ImageBoxMaterial::Color(ImageBoxColor {\n            color: Color {\n                r: 1.0,\n                g: 0.25,\n                b: 0.25,\n                a: 1.0,\n            },\n            ..Default::default()\n        }),\n        ..Default::default()\n    });\n\n    DeclarativeApp::simple(\"Image Box - Color\", tree);\n}\n"
  },
  {
    "path": "crates/_/examples/image_box_frame.rs",
    "content": "use raui_app::app::declarative::DeclarativeApp;\nuse raui_core::{\n    make_widget,\n    widget::{\n        component::image_box::{ImageBoxProps, image_box},\n        unit::image::{ImageBoxFrame, ImageBoxImage, ImageBoxImageScaling, ImageBoxMaterial},\n    },\n};\n\nfn main() {\n    let tree = make_widget!(image_box).with_props(ImageBoxProps {\n        material: ImageBoxMaterial::Image(ImageBoxImage {\n            id: \"./demos/in-game/resources/images/slider-background.png\".to_owned(),\n            // enable nine-slice by setting Frame scaling.\n            scaling: ImageBoxImageScaling::Frame(ImageBoxFrame {\n                // rectangle that describes margins of the frame of the source image texture.\n                source: 3.0.into(),\n                // rectangle that describes margins of the frame of the UI image being presented.\n                destination: 64.0.into(),\n                ..Default::default()\n            }),\n            ..Default::default()\n        }),\n        ..Default::default()\n    });\n\n    DeclarativeApp::simple(\"Image Box - Frame\", tree);\n}\n"
  },
  {
    "path": "crates/_/examples/image_box_image.rs",
    "content": "use raui_app::app::declarative::DeclarativeApp;\nuse raui_core::{\n    make_widget,\n    widget::{\n        component::image_box::{ImageBoxProps, image_box},\n        unit::image::{ImageBoxAspectRatio, ImageBoxImage, ImageBoxMaterial},\n    },\n};\n\nfn main() {\n    let tree = make_widget!(image_box).with_props(ImageBoxProps {\n        material: ImageBoxMaterial::Image(ImageBoxImage {\n            id: \"./demos/hello-world/resources/cats.jpg\".to_owned(),\n            ..Default::default()\n        }),\n        // makes internal image size keeping its aspect ratio.\n        content_keep_aspect_ratio: Some(ImageBoxAspectRatio {\n            // horizontal alignment of the content relative to the horizontal free space.\n            horizontal_alignment: 0.5,\n            // vertical alignment of the content relative to the vertical free space.\n            vertical_alignment: 0.5,\n            // if set to true then content instead of getting smaller to fit inside the layout box,\n            // it will \"leak\" outside of the layout box.\n            outside: true,\n        }),\n        ..Default::default()\n    });\n\n    DeclarativeApp::simple(\"Image Box - Image\", tree);\n}\n"
  },
  {
    "path": "crates/_/examples/image_box_procedural.rs",
    "content": "use raui_app::app::declarative::DeclarativeApp;\nuse raui_core::{\n    layout::CoordsMappingScaling,\n    make_widget,\n    widget::{\n        component::image_box::{ImageBoxProps, image_box},\n        unit::image::{ImageBoxMaterial, ImageBoxProcedural, ImageBoxProceduralVertex},\n        utils::{Color, Vec2},\n    },\n};\n\nfn main() {\n    let tree = make_widget!(image_box).with_props(ImageBoxProps {\n        // procedural image material allows to draw custom mesh with dedicated\n        // shader either from statics or from file.\n        // available static shaders:\n        // - `@pass`: simple pass through shader that ignores camera matrix.\n        // - `@colored`: shader that applies camera transform and color vertices.\n        // - `@textured`: shader that applies camera transform and texture with color vertices.\n        // if we want to use shader from files, assuming we have two files:\n        // - `path/to/shader.vs`\n        // - `path/to/shader.fs`\n        // then our id would be: `path/to/shader`.\n        material: ImageBoxMaterial::Procedural(\n            ImageBoxProcedural::new(\"@colored\")\n                // if we tell material to remap vertices from its local\n                // coordinate space to rendered screen space.\n                // Here we keep mesh inside image box keeping aspect ratio.\n                .vertex_mapping(CoordsMappingScaling::FitToView(\n                    Vec2 { x: 1.0, y: 1.0 },\n                    true,\n                ))\n                .quad([\n                    ImageBoxProceduralVertex {\n                        position: Vec2 { x: 0.5, y: 0.0 },\n                        color: Color {\n                            r: 1.0,\n                            g: 0.0,\n                            b: 0.0,\n                            a: 1.0,\n                        },\n                        ..Default::default()\n                    },\n                    ImageBoxProceduralVertex {\n                        position: Vec2 { x: 1.0, y: 0.5 },\n                        color: Color {\n                            r: 0.0,\n                            g: 1.0,\n                            b: 0.0,\n                            a: 1.0,\n                        },\n                        ..Default::default()\n                    },\n                    ImageBoxProceduralVertex {\n                        position: Vec2 { x: 0.5, y: 1.0 },\n                        color: Color {\n                            r: 1.0,\n                            g: 1.0,\n                            b: 0.0,\n                            a: 1.0,\n                        },\n                        ..Default::default()\n                    },\n                    ImageBoxProceduralVertex {\n                        position: Vec2 { x: 0.0, y: 0.5 },\n                        color: Color {\n                            r: 0.0,\n                            g: 0.0,\n                            b: 1.0,\n                            a: 1.0,\n                        },\n                        ..Default::default()\n                    },\n                ]),\n        ),\n        ..Default::default()\n    });\n\n    DeclarativeApp::simple(\"Image Box - Procedural\", tree);\n}\n"
  },
  {
    "path": "crates/_/examples/immediate_mode.rs",
    "content": "// Example of immediate mode UI on top of RAUI.\n// It's goal is to bring more ergonomics to RAUI by hiding\n// declarative interface under simple nested function calls.\n// As with retained mode, immediate mode UI can be mixed with\n// declarative mode and retained mode widgets.\n\nuse raui_app::app::immediate::ImmediateApp;\nuse raui_core::{\n    Scalar,\n    widget::{\n        component::{\n            containers::{\n                horizontal_box::HorizontalBoxProps, vertical_box::VerticalBoxProps,\n                wrap_box::WrapBoxProps,\n            },\n            image_box::ImageBoxProps,\n            interactive::{\n                input_field::{TextInputMode, input_text_with_cursor},\n                navigation::NavItemActive,\n            },\n            text_box::TextBoxProps,\n        },\n        unit::{flex::FlexBoxItemLayout, text::TextBoxFont},\n        utils::Color,\n    },\n};\nuse raui_immediate::{ImProps, apply};\nuse raui_immediate_widgets::core::{\n    containers::{content_box, horizontal_box, nav_vertical_box, wrap_box},\n    image_box,\n    interactive::{ImmediateButton, button, input_field, self_tracking},\n    text_box,\n};\n\nconst FONT: &str = \"./demos/hello-world/resources/verdana.ttf\";\n\n// app function widget, we pass application state there.\npub fn app(value: &mut usize) {\n    let props = WrapBoxProps {\n        margin: 20.0.into(),\n        ..Default::default()\n    };\n\n    wrap_box(props, || {\n        let props = VerticalBoxProps {\n            separation: 50.0,\n            ..Default::default()\n        };\n\n        // we can use any \"immedietified\" RAUI widget we want.\n        // we can pass Props to parameterize RAUI widget in first param.\n        // BTW. we should make sure to use any `nav_*` container widget\n        // somewhere in the app root to make app interactive.\n        nav_vertical_box(props, || {\n            let layout = FlexBoxItemLayout {\n                basis: Some(48.0),\n                grow: 0.0,\n                shrink: 0.0,\n                ..Default::default()\n            };\n\n            // we can also apply props on all produced widgets in the scope.\n            apply(ImProps(layout), || {\n                counter(value);\n\n                let props = HorizontalBoxProps {\n                    separation: 50.0,\n                    ..Default::default()\n                };\n\n                horizontal_box(props, || {\n                    // we can react to button-like behavior by reading what\n                    // button-like widgets return of their tracked state.\n                    if text_button(\"Increment\").trigger_start() {\n                        *value = value.saturating_add(1);\n                    }\n\n                    if text_button(\"Decrement\").trigger_start() {\n                        *value = value.saturating_sub(1);\n                    }\n                });\n            });\n\n            self_tracking((), |tracking| {\n                image_box(ImageBoxProps::colored(Color {\n                    r: tracking.state.factor.x,\n                    g: 0.0,\n                    b: tracking.state.factor.y,\n                    a: 1.0,\n                }));\n            });\n        });\n    });\n}\n\nfn text_button(text: &str) -> ImmediateButton {\n    // buttons use `use_state` hook under the hood to track\n    // declarative mode button state, that's copy of being\n    // returned from button function and passed into its\n    // group closure for children widgets to use.\n    // BTW. don't forget to apply `NavItemActive` props on\n    // button if you want to have it enabled for navigation.\n    button(NavItemActive, |state| {\n        content_box((), || {\n            image_box(ImageBoxProps::colored(Color {\n                r: if state.state.selected { 1.0 } else { 0.75 },\n                g: if state.state.trigger { 1.0 } else { 0.75 },\n                b: if state.state.context { 1.0 } else { 0.75 },\n                a: 1.0,\n            }));\n\n            text_box(TextBoxProps {\n                text: text.to_string(),\n                font: TextBoxFont {\n                    name: FONT.to_owned(),\n                    size: 32.0,\n                },\n                color: Color {\n                    r: 0.0,\n                    g: 0.0,\n                    b: 0.0,\n                    a: 1.0,\n                },\n                ..Default::default()\n            });\n        });\n    })\n}\n\nfn counter(value: &mut usize) {\n    // counter widget is a text box wrapped in an input field.\n    // it works like combination of button (can be focused by\n    // selection/navigation) and text field (collects keyboard\n    // text characters when focused).\n    let props = (NavItemActive, TextInputMode::UnsignedInteger);\n\n    let (result, ..) = input_field(value, props, |text, state, button| {\n        text_box(TextBoxProps {\n            text: if state.focused {\n                input_text_with_cursor(text, state.cursor_position, '|')\n            } else if text.is_empty() {\n                \"...\".to_owned()\n            } else {\n                text.to_owned()\n            },\n            font: TextBoxFont {\n                name: FONT.to_owned(),\n                size: 32.0,\n            },\n            color: Color {\n                r: Scalar::from(button.state.trigger),\n                g: Scalar::from(button.state.selected),\n                b: Scalar::from(state.focused),\n                a: 1.0,\n            },\n            ..Default::default()\n        });\n    });\n\n    if let Some(result) = result {\n        *value = result;\n    }\n}\n\nfn main() {\n    // some applciation state.\n    let mut counter = 0usize;\n\n    ImmediateApp::simple(\"Immediate mode UI\", move |_| {\n        app(&mut counter);\n    });\n}\n"
  },
  {
    "path": "crates/_/examples/immediate_mode_access_and_tests.rs",
    "content": "use raui_app::app::immediate::ImmediateApp;\nuse raui_core::widget::{\n    component::{image_box::ImageBoxProps, interactive::navigation::NavItemActive},\n    utils::Color,\n};\nuse raui_immediate::{register_access, use_access};\nuse raui_immediate_widgets::core::{\n    containers::nav_content_box,\n    image_box,\n    interactive::{ImmediateButton, button},\n};\n\npub fn app() {\n    nav_content_box((), || {\n        clickable_button();\n    });\n}\n\npub fn clickable_button() {\n    if colored_button().trigger_start() {\n        // we use access point to some host data\n        let clicked = use_access::<bool>(\"clicked\");\n\n        *clicked.write().unwrap() = true;\n    }\n}\n\nfn colored_button() -> ImmediateButton {\n    button(NavItemActive, |state| {\n        let props = ImageBoxProps::colored(if state.state.trigger {\n            Color {\n                r: 0.5,\n                g: 0.0,\n                b: 0.0,\n                a: 1.0,\n            }\n        } else {\n            Color {\n                r: 0.0,\n                g: 0.5,\n                b: 0.0,\n                a: 1.0,\n            }\n        });\n\n        image_box(props);\n    })\n}\n\nfn main() {\n    let mut clicked = false;\n\n    ImmediateApp::simple(\"Immediate mode UI - Access and tests\", move |_| {\n        // here we register access point to some game state\n        let _lifetime = register_access(\"clicked\", &mut clicked);\n\n        app();\n    });\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use raui_core::{\n        interactive::default_interactions_engine::{Interaction, PointerButton},\n        layout::CoordsMapping,\n        tester::AppCycleTester,\n        widget::utils::Rect,\n    };\n    use raui_immediate::ImmediateContext;\n\n    #[test]\n    fn test_tracked_button() {\n        let mut tester = AppCycleTester::new(\n            CoordsMapping::new(Rect {\n                left: 0.0,\n                right: 1024.0,\n                top: 0.0,\n                bottom: 576.0,\n            }),\n            ImmediateContext::default(),\n        );\n        let mut mock = false;\n\n        tester\n            .interactions_engine\n            .interact(Interaction::PointerDown(\n                PointerButton::Trigger,\n                [100.0, 100.0].into(),\n            ));\n\n        // since RAUI has deferred UI resolution, signal will take\n        // few frames to go through declarative layer to immediate\n        // layer and then back to user site.\n        for _ in 0..4 {\n            tester.run_frame(ImmediateApp::test_frame(|| {\n                // and here we register access point to mock data\n                let _lifetime = register_access(\"clicked\", &mut mock);\n\n                app();\n            }));\n        }\n\n        assert_eq!(mock, true);\n    }\n}\n"
  },
  {
    "path": "crates/_/examples/immediate_mode_stack_props.rs",
    "content": "use raui_app::app::immediate::ImmediateApp;\nuse raui_core::widget::{\n    component::text_box::TextBoxProps,\n    unit::{\n        flex::FlexBoxItemLayout,\n        text::{TextBoxFont, TextBoxHorizontalAlign},\n    },\n    utils::Color,\n};\nuse raui_immediate::{ImProps, ImStackProps, apply, use_stack_props};\nuse raui_immediate_widgets::core::{containers::nav_vertical_box, text_box};\n\npub fn app() {\n    let props = TextBoxProps {\n        font: TextBoxFont {\n            name: \"./demos/hello-world/resources/verdana.ttf\".to_owned(),\n            size: 96.0,\n        },\n        color: Color {\n            r: 1.0,\n            g: 0.0,\n            b: 0.0,\n            a: 1.0,\n        },\n        ..Default::default()\n    };\n\n    // We can create cascaded styling with stack props.\n    // The difference between stack props and applied props\n    // is that applied props are applied directly do its\n    // children nodes, while stack props are stacked so any\n    // widget in hierarchy can access the top of the props\n    // stack - we can easily share style down the hierarchy!\n    apply(ImStackProps::new(props), || {\n        nav_vertical_box((), || {\n            let layout = FlexBoxItemLayout {\n                basis: Some(100.0),\n                grow: 0.0,\n                shrink: 0.0,\n                margin: 32.0.into(),\n                ..Default::default()\n            };\n\n            // These props apply only to label widgets.\n            apply(ImProps(layout), || {\n                label(\"Hey!\");\n                label(\"Hi!\");\n\n                let props = TextBoxProps {\n                    font: TextBoxFont {\n                        name: \"./demos/in-game/resources/fonts/MiKrollFantasy.ttf\".to_owned(),\n                        size: 100.0,\n                    },\n                    color: Color {\n                        r: 0.0,\n                        g: 0.0,\n                        b: 1.0,\n                        a: 1.0,\n                    },\n                    horizontal_align: TextBoxHorizontalAlign::Center,\n                    ..Default::default()\n                };\n\n                // By pushing new props on stack props we override\n                // what's gonna be used in all chidren in hierarchy.\n                apply(ImStackProps::new(props), || {\n                    label(\"Hello!\");\n                    label(\"Ohayo?\");\n                });\n            });\n        });\n    });\n}\n\npub fn label(text: impl ToString) {\n    // Accessing props from the stack to achieve cascading styles.\n    let mut props = use_stack_props::<TextBoxProps>().unwrap_or_default();\n    props.text = text.to_string();\n\n    text_box(props);\n}\n\nfn main() {\n    ImmediateApp::simple(\"Immediate mode UI - Stack props\", |_| app());\n}\n"
  },
  {
    "path": "crates/_/examples/immediate_mode_states_and_effects.rs",
    "content": "// Make sure you have seen `immediate_mode` code example first, because this is a continuation of that.\n\nuse raui_app::app::immediate::ImmediateApp;\nuse raui_core::widget::{\n    component::{\n        containers::wrap_box::WrapBoxProps, image_box::ImageBoxProps,\n        interactive::navigation::NavItemActive, text_box::TextBoxProps,\n    },\n    unit::text::TextBoxFont,\n    utils::Color,\n};\nuse raui_immediate::{ImmediateOnMount, ImmediateOnUnmount, use_effects, use_state};\nuse raui_immediate_widgets::core::{\n    containers::{content_box, nav_vertical_box, wrap_box},\n    image_box,\n    interactive::{ImmediateButton, button},\n    text_box,\n};\n\nconst FONT: &str = \"./demos/hello-world/resources/verdana.ttf\";\n\npub fn app() {\n    let props = WrapBoxProps {\n        margin: 20.0.into(),\n        ..Default::default()\n    };\n\n    wrap_box(props, || {\n        nav_vertical_box((), || {\n            // `use_state` allows to keep persistent state across\n            // multiple frames, as long as order of calls and types\n            // match between frames.\n            let flag = use_state(|| false);\n            let mut flag = flag.write().unwrap();\n\n            let counter = use_state(|| 0usize);\n            let counter_mount = counter.clone();\n\n            if text_button(\"Toggle\").trigger_start() {\n                *flag = !*flag;\n            }\n\n            if *flag {\n                // effects are passed as props, these are callbacks\n                // that get executed whenever RAUI widget gets mounted,\n                // unmounted or changed.\n                // There is also `ImmediateHooks` props that allow to\n                // apply RAUI hooks to rendered widget, useful for example\n                // to render effects widget with any custom behavior.\n                let effects = (\n                    ImmediateOnMount::new(move || {\n                        println!(\"Mounted!\");\n                        *counter_mount.write().unwrap() += 1;\n                    }),\n                    ImmediateOnUnmount::new(|| {\n                        println!(\"Unmounted!\");\n                    }),\n                );\n\n                use_effects(effects, || {\n                    label(format!(\"Mounted {} times!\", *counter.read().unwrap()));\n                });\n            }\n        });\n    });\n}\n\nfn label(text: impl ToString) {\n    text_box(TextBoxProps {\n        text: text.to_string(),\n        font: TextBoxFont {\n            name: crate::FONT.to_owned(),\n            size: 32.0,\n        },\n        color: Color {\n            r: 0.0,\n            g: 0.0,\n            b: 0.0,\n            a: 1.0,\n        },\n        ..Default::default()\n    });\n}\n\nfn text_button(text: &str) -> ImmediateButton {\n    button(NavItemActive, |state| {\n        content_box((), || {\n            image_box(ImageBoxProps::colored(Color {\n                r: if state.state.selected { 1.0 } else { 0.75 },\n                g: if state.state.trigger { 1.0 } else { 0.75 },\n                b: if state.state.context { 1.0 } else { 0.75 },\n                a: 1.0,\n            }));\n\n            text_box(TextBoxProps {\n                text: text.to_string(),\n                font: TextBoxFont {\n                    name: crate::FONT.to_owned(),\n                    size: 32.0,\n                },\n                color: Color {\n                    r: 0.0,\n                    g: 0.0,\n                    b: 0.0,\n                    a: 1.0,\n                },\n                ..Default::default()\n            });\n        });\n    })\n}\n\nfn main() {\n    ImmediateApp::simple(\"Immediate mode UI - States and Effects\", |_| {\n        app();\n    });\n}\n"
  },
  {
    "path": "crates/_/examples/immediate_text_field_paper.rs",
    "content": "use raui_app::app::immediate::ImmediateApp;\nuse raui_core::widget::{\n    component::{containers::size_box::SizeBoxProps, interactive::navigation::NavItemActive},\n    unit::{size::SizeBoxSizeValue, text::TextBoxFont},\n    utils::Rect,\n};\nuse raui_immediate::{ImSharedProps, apply, use_state};\nuse raui_immediate_widgets::{\n    core::containers::size_box,\n    material::{containers::nav_paper, interactive::text_field_paper},\n};\nuse raui_material::{\n    component::interactive::text_field_paper::TextFieldPaperProps,\n    theme::{ThemeColor, ThemeProps, ThemedTextMaterial, ThemedWidgetProps, new_dark_theme},\n};\n\n// Create a new theme with a custom text variant for input fields.\nfn new_theme() -> ThemeProps {\n    let mut theme = new_dark_theme();\n    theme.text_variants.insert(\n        \"input\".to_owned(),\n        ThemedTextMaterial {\n            font: TextBoxFont {\n                name: \"./demos/hello-world/resources/verdana.ttf\".to_owned(),\n                size: 24.0,\n            },\n            ..Default::default()\n        },\n    );\n    theme\n}\n\nfn main() {\n    ImmediateApp::simple(\"Immediate mode Text Field Paper\", |_| {\n        // Apply the custom theme for all UI widgets.\n        apply(ImSharedProps(new_theme()), || {\n            // Make navigable paper container for the text field.\n            // Navigable containers are required to make interactive widgets work.\n            nav_paper((), || {\n                let props = SizeBoxProps {\n                    width: SizeBoxSizeValue::Fill,\n                    height: SizeBoxSizeValue::Exact(50.0),\n                    margin: 20.0.into(),\n                    ..Default::default()\n                };\n\n                size_box(props, || {\n                    let props = (\n                        TextFieldPaperProps {\n                            hint: \"> Type some text...\".to_owned(),\n                            paper_theme: ThemedWidgetProps {\n                                color: ThemeColor::Primary,\n                                ..Default::default()\n                            },\n                            padding: Rect {\n                                left: 10.0,\n                                right: 10.0,\n                                top: 6.0,\n                                bottom: 6.0,\n                            },\n                            variant: \"input\".to_owned(),\n                            ..Default::default()\n                        },\n                        NavItemActive,\n                    );\n\n                    // Make state holding the text input value.\n                    let text = use_state(|| \"Hello!\".to_owned());\n\n                    // Make the text field paper with the text input state and\n                    // override existing value on change.\n                    let value = text_field_paper(&*text.read().unwrap(), props).0;\n                    if let Some(value) = value {\n                        *text.write().unwrap() = value;\n                    }\n                });\n            });\n        });\n    });\n}\n"
  },
  {
    "path": "crates/_/examples/input_field.rs",
    "content": "use raui_app::app::{App, AppConfig, declarative::DeclarativeApp};\nuse raui_core::{\n    Managed, Scalar, make_widget, pre_hooks,\n    view_model::{ViewModel, ViewModelValue},\n    widget::{\n        component::{\n            containers::vertical_box::vertical_box,\n            interactive::{\n                button::{\n                    ButtonNotifyMessage, ButtonNotifyProps, ButtonProps, use_button_notified_state,\n                },\n                input_field::{\n                    TextInputControlNotifyMessage, TextInputControlNotifyProps, TextInputMode,\n                    TextInputNotifyMessage, TextInputNotifyProps, TextInputProps, TextInputState,\n                    input_field, input_text_with_cursor, use_text_input_notified_state,\n                },\n                navigation::{NavItemActive, use_nav_container_active},\n            },\n            text_box::{TextBoxProps, text_box},\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::text::{TextBoxFont, TextBoxSizeValue},\n        utils::Color,\n    },\n};\n\nconst DATA: &str = \"data\";\nconst TEXT_INPUT: &str = \"text-input\";\nconst NUMBER_INPUT: &str = \"number-input\";\nconst INTEGER_INPUT: &str = \"integer-input\";\nconst UNSIGNED_INTEGER_INPUT: &str = \"unsigned-integer-input\";\nconst FILTER_INPUT: &str = \"filter-input\";\n\nstruct AppData {\n    text_input: Managed<ViewModelValue<String>>,\n    number_input: Managed<ViewModelValue<String>>,\n    integer_input: Managed<ViewModelValue<String>>,\n    unsigned_integer_input: Managed<ViewModelValue<String>>,\n    filter_input: Managed<ViewModelValue<String>>,\n}\n\nfn use_app(ctx: &mut WidgetContext) {\n    ctx.life_cycle.mount(|mut ctx| {\n        ctx.view_models\n            .bindings(DATA, TEXT_INPUT)\n            .unwrap()\n            .bind(ctx.id.to_owned());\n        ctx.view_models\n            .bindings(DATA, NUMBER_INPUT)\n            .unwrap()\n            .bind(ctx.id.to_owned());\n        ctx.view_models\n            .bindings(DATA, INTEGER_INPUT)\n            .unwrap()\n            .bind(ctx.id.to_owned());\n        ctx.view_models\n            .bindings(DATA, UNSIGNED_INTEGER_INPUT)\n            .unwrap()\n            .bind(ctx.id.to_owned());\n        ctx.view_models\n            .bindings(DATA, FILTER_INPUT)\n            .unwrap()\n            .bind(ctx.id.to_owned());\n    });\n}\n\n// we mark root widget as navigable container to let user focus and type in text inputs.\n#[pre_hooks(use_nav_container_active, use_app)]\nfn app(mut ctx: WidgetContext) -> WidgetNode {\n    let mut app_data = ctx\n        .view_models\n        .view_model_mut(DATA)\n        .unwrap()\n        .write::<AppData>()\n        .unwrap();\n\n    // put inputs with all different types modes.\n    make_widget!(vertical_box)\n        .listed_slot(\n            make_widget!(input)\n                .with_props(TextInputMode::Text)\n                .with_props(TextInputProps {\n                    allow_new_line: false,\n                    text: Some(app_data.text_input.lazy().into()),\n                }),\n        )\n        .listed_slot(\n            make_widget!(input)\n                .with_props(TextInputMode::Number)\n                .with_props(TextInputProps {\n                    allow_new_line: false,\n                    text: Some(app_data.number_input.lazy().into()),\n                }),\n        )\n        .listed_slot(\n            make_widget!(input)\n                .with_props(TextInputMode::Integer)\n                .with_props(TextInputProps {\n                    allow_new_line: false,\n                    text: Some(app_data.integer_input.lazy().into()),\n                }),\n        )\n        .listed_slot(\n            make_widget!(input)\n                .with_props(TextInputMode::UnsignedInteger)\n                .with_props(TextInputProps {\n                    allow_new_line: false,\n                    text: Some(app_data.unsigned_integer_input.lazy().into()),\n                }),\n        )\n        .listed_slot(\n            make_widget!(input)\n                .with_props(TextInputMode::Filter(|_, character| {\n                    character.is_uppercase()\n                }))\n                .with_props(TextInputProps {\n                    allow_new_line: false,\n                    text: Some(app_data.filter_input.lazy().into()),\n                }),\n        )\n        .into()\n}\n\nfn use_input(ctx: &mut WidgetContext) {\n    ctx.life_cycle.change(|ctx| {\n        for msg in ctx.messenger.messages {\n            if let Some(msg) = msg.as_any().downcast_ref::<TextInputNotifyMessage>() {\n                println!(\"* Text input: {msg:#?}\");\n            } else if let Some(msg) = msg.as_any().downcast_ref::<TextInputControlNotifyMessage>() {\n                println!(\"* Text input control: {msg:#?}\");\n            } else if let Some(msg) = msg.as_any().downcast_ref::<ButtonNotifyMessage>() {\n                println!(\"* Button: {msg:#?}\");\n            }\n        }\n    });\n}\n\n// this component will receive and store button and input text state changes.\n#[pre_hooks(use_button_notified_state, use_text_input_notified_state, use_input)]\nfn input(mut ctx: WidgetContext) -> WidgetNode {\n    let ButtonProps {\n        selected, trigger, ..\n    } = ctx.state.read_cloned_or_default();\n\n    let TextInputState {\n        cursor_position,\n        focused,\n    } = ctx.state.read_cloned_or_default();\n\n    let TextInputProps {\n        allow_new_line,\n        text,\n    } = ctx.props.read_cloned_or_default();\n\n    let mode = ctx.props.read_cloned_or_default::<TextInputMode>();\n\n    let value = text\n        .as_ref()\n        .and_then(|text| mode.process(&text.get()))\n        .unwrap_or_default();\n\n    // input field is an evolution of input text, what changes is input field can be focused\n    // because it is input text plus button.\n    make_widget!(input_field)\n        // as usually we enable this navigation item.\n        .with_props(NavItemActive)\n        // pass text input mode to the input field (by default Text mode is used).\n        .with_props(mode)\n        // setup text input.\n        .with_props(TextInputProps {\n            allow_new_line,\n            text,\n        })\n        // notify this component about input text state change.\n        .with_props(TextInputNotifyProps(ctx.id.to_owned().into()))\n        // notify this component about input control characters it receives.\n        // useful for reacting to Tab key for example.\n        .with_props(TextInputControlNotifyProps(ctx.id.to_owned().into()))\n        // notify this component about button state change.\n        .with_props(ButtonNotifyProps(ctx.id.to_owned().into()))\n        .named_slot(\n            \"content\",\n            // input field and input text components doesn't assume any content widget for you so\n            // that's why we create custom input component to make it work and look exactly as we\n            // want - here we just put a text box.\n            make_widget!(text_box).with_props(TextBoxProps {\n                text: if focused {\n                    input_text_with_cursor(&value, cursor_position, '|')\n                } else if value.is_empty() {\n                    match mode {\n                        TextInputMode::Text => \"> Type text...\".to_owned(),\n                        TextInputMode::Number => \"> Type number...\".to_owned(),\n                        TextInputMode::Integer => \"> Type integer...\".to_owned(),\n                        TextInputMode::UnsignedInteger => \"> Type unsigned integer...\".to_owned(),\n                        TextInputMode::Filter(_) => \"> Type uppercase text...\".to_owned(),\n                    }\n                } else {\n                    value\n                },\n                width: TextBoxSizeValue::Fill,\n                height: TextBoxSizeValue::Exact(48.0),\n                font: TextBoxFont {\n                    name: \"./demos/hello-world/resources/verdana.ttf\".to_owned(),\n                    size: 32.0,\n                },\n                color: Color {\n                    r: Scalar::from(trigger),\n                    g: Scalar::from(selected),\n                    b: Scalar::from(focused),\n                    a: 1.0,\n                },\n                ..Default::default()\n            }),\n        )\n        .into()\n}\n\nfn main() {\n    let app = DeclarativeApp::default()\n        .tree(make_widget!(app))\n        .view_model(\n            DATA,\n            ViewModel::produce(|properties| AppData {\n                text_input: Managed::new(ViewModelValue::new(\n                    Default::default(),\n                    properties.notifier(TEXT_INPUT),\n                )),\n                number_input: Managed::new(ViewModelValue::new(\n                    Default::default(),\n                    properties.notifier(NUMBER_INPUT),\n                )),\n                integer_input: Managed::new(ViewModelValue::new(\n                    Default::default(),\n                    properties.notifier(INTEGER_INPUT),\n                )),\n                unsigned_integer_input: Managed::new(ViewModelValue::new(\n                    Default::default(),\n                    properties.notifier(UNSIGNED_INTEGER_INPUT),\n                )),\n                filter_input: Managed::new(ViewModelValue::new(\n                    Default::default(),\n                    properties.notifier(FILTER_INPUT),\n                )),\n            }),\n        );\n\n    App::new(AppConfig::default().title(\"Input Field\")).run(app);\n}\n"
  },
  {
    "path": "crates/_/examples/navigation.rs",
    "content": "use raui_app::app::{App, AppConfig, declarative::DeclarativeApp};\nuse raui_core::{\n    make_widget, pre_hooks,\n    widget::{\n        component::{\n            containers::{\n                horizontal_box::{HorizontalBoxProps, horizontal_box},\n                vertical_box::{VerticalBoxProps, vertical_box},\n            },\n            image_box::{ImageBoxProps, image_box},\n            interactive::{\n                button::{ButtonProps, button},\n                navigation::{\n                    NavAutoSelect, NavItemActive, use_nav_container_active,\n                    use_nav_jump_direction_active,\n                },\n            },\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::flex::FlexBoxItemLayout,\n        utils::Color,\n    },\n};\n\n#[pre_hooks(use_nav_container_active, use_nav_jump_direction_active)]\nfn app(mut ctx: WidgetContext) -> WidgetNode {\n    let slots_layout = FlexBoxItemLayout {\n        margin: 20.0.into(),\n        ..Default::default()\n    };\n\n    make_widget!(vertical_box)\n        .key(\"vertical\")\n        .with_props(VerticalBoxProps {\n            override_slots_layout: Some(slots_layout.clone()),\n            ..Default::default()\n        })\n        .listed_slot(\n            make_widget!(horizontal_box)\n                .key(\"horizontal\")\n                .with_props(HorizontalBoxProps {\n                    override_slots_layout: Some(slots_layout),\n                    ..Default::default()\n                })\n                .listed_slot(make_widget!(button_item).key(\"a\").with_props(NavAutoSelect))\n                .listed_slot(make_widget!(button_item).key(\"b\"))\n                .listed_slot(make_widget!(button_item).key(\"c\")),\n        )\n        .listed_slot(make_widget!(button_item).key(\"d\"))\n        .listed_slot(make_widget!(button_item).key(\"e\"))\n        .into()\n}\n\nfn button_item(ctx: WidgetContext) -> WidgetNode {\n    make_widget!(button)\n        .key(\"button\")\n        .merge_props(ctx.props.clone())\n        .with_props(NavItemActive)\n        .named_slot(\"content\", make_widget!(button_content))\n        .into()\n}\n\nfn button_content(ctx: WidgetContext) -> WidgetNode {\n    let ButtonProps {\n        selected,\n        trigger,\n        context,\n        ..\n    } = ctx.props.read_cloned_or_default();\n\n    let color = if trigger {\n        Color {\n            r: 1.0,\n            g: 0.25,\n            b: 0.25,\n            a: 1.0,\n        }\n    } else if context {\n        Color {\n            r: 0.25,\n            g: 1.0,\n            b: 0.25,\n            a: 1.0,\n        }\n    } else if selected {\n        Color {\n            r: 0.25,\n            g: 0.25,\n            b: 1.0,\n            a: 1.0,\n        }\n    } else {\n        Color {\n            r: 0.25,\n            g: 0.25,\n            b: 0.25,\n            a: 1.0,\n        }\n    };\n\n    make_widget!(image_box)\n        .key(\"image\")\n        .with_props(ImageBoxProps::colored(color))\n        .into()\n}\n\nfn main() {\n    App::new(AppConfig::default().title(\"Navigation\")).run(\n        DeclarativeApp::default()\n            .tree(make_widget!(app).key(\"app\"))\n            .setup_interactions(|interactions| {\n                interactions.engine.deselect_when_no_button_found = false;\n            }),\n    );\n}\n"
  },
  {
    "path": "crates/_/examples/options_view.rs",
    "content": "use raui_app::app::{App, AppConfig, declarative::DeclarativeApp};\nuse raui_core::{\n    Managed, make_widget, pre_hooks,\n    view_model::{ViewModel, ViewModelValue},\n    widget::{\n        WidgetRef,\n        component::{\n            containers::{\n                anchor_box::PivotBoxProps, content_box::content_box, portal_box::PortalsContainer,\n                size_box::SizeBoxProps, vertical_box::vertical_box,\n            },\n            image_box::{ImageBoxProps, image_box},\n            interactive::{\n                button::ButtonProps,\n                navigation::{NavItemActive, use_nav_container_active},\n                options_view::{OptionsViewMode, OptionsViewProps, options_view},\n            },\n            text_box::{TextBoxProps, text_box},\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::{\n            content::ContentBoxItemLayout,\n            size::SizeBoxSizeValue,\n            text::{TextBoxFont, TextBoxHorizontalAlign, TextBoxVerticalAlign},\n        },\n        utils::{Color, Rect},\n    },\n};\n\nconst DATA: &str = \"data\";\nconst INDEX: &str = \"index\";\n\nstruct AppData {\n    index: Managed<ViewModelValue<usize>>,\n}\n\nfn use_app(ctx: &mut WidgetContext) {\n    ctx.life_cycle.mount(|mut ctx| {\n        ctx.view_models\n            .bindings(DATA, INDEX)\n            .unwrap()\n            .bind(ctx.id.to_owned());\n    });\n}\n\n#[pre_hooks(use_nav_container_active, use_app)]\nfn app(mut ctx: WidgetContext) -> WidgetNode {\n    let idref = WidgetRef::default();\n    let mut app_data = ctx\n        .view_models\n        .view_model_mut(DATA)\n        .unwrap()\n        .write::<AppData>()\n        .unwrap();\n\n    // We use content box marked with portals container as root to provide space\n    // for option views to anchor thier content into.\n    make_widget!(content_box)\n        .idref(idref.clone())\n        .with_shared_props(PortalsContainer(idref))\n        .listed_slot(\n            // Options view is basically a button that toggles its content anchored\n            // to itself. You can think of dropdown/context menus, but actually it\n            // can present any user widgets, not only in a list - content widget can\n            // be anything that takes listed slots and layouts them in some fashion.\n            make_widget!(options_view)\n                .with_props(ContentBoxItemLayout {\n                    anchors: 0.25.into(),\n                    margin: Rect {\n                        left: -150.0,\n                        right: -150.0,\n                        top: -30.0,\n                        bottom: -30.0,\n                    },\n                    ..Default::default()\n                })\n                // Here we provide options view index source, which tells which option\n                // has to be shown.\n                .with_props(OptionsViewProps {\n                    input: Some(app_data.index.lazy().into()),\n                })\n                .with_props(NavItemActive)\n                // Here we tell how to anchor content relatively to options box button.\n                .with_props(PivotBoxProps {\n                    pivot: [0.0, 1.0].into(),\n                    align: 0.0.into(),\n                })\n                // Additionally we might want to provide size of the content.\n                .with_props(SizeBoxProps {\n                    width: SizeBoxSizeValue::Exact(300.0),\n                    height: SizeBoxSizeValue::Exact(400.0),\n                    ..Default::default()\n                })\n                // Here we provide content widget. Preferably without existing children,\n                // because options will be appended, not replacing old children.\n                // Lists are obvious choice but you could also put slots into a grid,\n                // or even freeform content box to for example make a map with city\n                // icons to select!\n                .named_slot(\n                    \"content\",\n                    // Since this list will be injected into portal container, which is\n                    // content box, we can make that list kept in bounds of the container.\n                    make_widget!(vertical_box).with_props(ContentBoxItemLayout {\n                        keep_in_bounds: true.into(),\n                        ..Default::default()\n                    }),\n                )\n                // And last but not least, we provide items as listed slots.\n                // Each provided widget will be wrapped in button that will notify\n                // options view about selected option.\n                .listed_slot(\n                    make_widget!(option)\n                        .with_props(\"Hello\".to_owned())\n                        .with_props(NavItemActive),\n                )\n                .listed_slot(\n                    make_widget!(option)\n                        .with_props(\"World\".to_owned())\n                        .with_props(NavItemActive),\n                )\n                .listed_slot(\n                    make_widget!(option)\n                        .with_props(\"this\".to_owned())\n                        .with_props(NavItemActive),\n                )\n                .listed_slot(\n                    make_widget!(option)\n                        .with_props(\"is\".to_owned())\n                        .with_props(NavItemActive),\n                )\n                .listed_slot(\n                    make_widget!(option)\n                        .with_props(\"dropdown\".to_owned())\n                        .with_props(NavItemActive),\n                ),\n        )\n        .into()\n}\n\nfn option(ctx: WidgetContext) -> WidgetNode {\n    // Since options are wrapped in buttons, we can read their button state and use it.\n    let ButtonProps {\n        selected, trigger, ..\n    } = ctx.props.read_cloned_or_default();\n    let color = if trigger {\n        Color {\n            r: 1.0,\n            g: 0.0,\n            b: 0.0,\n            a: 1.0,\n        }\n    } else if selected {\n        Color {\n            r: 0.0,\n            g: 0.0,\n            b: 1.0,\n            a: 1.0,\n        }\n    } else {\n        Color {\n            r: 0.0,\n            g: 0.0,\n            b: 0.0,\n            a: 1.0,\n        }\n    };\n    let text = ctx.props.read_cloned_or_default::<String>();\n    // We can also read options view mode property to render our option widget\n    // diferently, depending if option is shown as selected or as content item.\n    let text = match ctx.props.read_cloned_or_default::<OptionsViewMode>() {\n        OptionsViewMode::Selected => format!(\"> {text}\"),\n        OptionsViewMode::Option => format!(\"# {text}\"),\n    };\n\n    make_widget!(content_box)\n        .listed_slot(make_widget!(image_box).with_props(ImageBoxProps::colored(color)))\n        .listed_slot(make_widget!(text_box).with_props(TextBoxProps {\n            text,\n            font: TextBoxFont {\n                name: \"./demos/hello-world/resources/verdana.ttf\".to_owned(),\n                size: 32.0,\n            },\n            horizontal_align: TextBoxHorizontalAlign::Center,\n            vertical_align: TextBoxVerticalAlign::Middle,\n            color: Color {\n                r: 1.0,\n                g: 1.0,\n                b: 1.0,\n                a: 1.0,\n            },\n            ..Default::default()\n        }))\n        .into()\n}\n\nfn main() {\n    let app = DeclarativeApp::default()\n        .tree(make_widget!(app))\n        .view_model(\n            DATA,\n            ViewModel::produce(|properties| AppData {\n                index: Managed::new(ViewModelValue::new(\n                    Default::default(),\n                    properties.notifier(INDEX),\n                )),\n            }),\n        );\n\n    App::new(AppConfig::default().title(\"Options View\")).run(app);\n}\n"
  },
  {
    "path": "crates/_/examples/options_view_map.rs",
    "content": "// Make sure you have seen `options_view` code example first, because this is an evolution of that.\n\nuse raui_app::app::{App, AppConfig, declarative::DeclarativeApp};\nuse raui_core::{\n    Managed, Scalar, make_widget, pre_hooks,\n    view_model::{ViewModel, ViewModelValue},\n    widget::{\n        WidgetRef,\n        component::{\n            containers::{\n                anchor_box::PivotBoxProps, content_box::content_box, portal_box::PortalsContainer,\n                size_box::SizeBoxProps,\n            },\n            image_box::{ImageBoxProps, image_box},\n            interactive::{\n                button::ButtonProps,\n                navigation::{NavItemActive, use_nav_container_active},\n                options_view::{OptionsViewMode, OptionsViewProps, options_view},\n            },\n            text_box::{TextBoxProps, text_box},\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::{\n            content::ContentBoxItemLayout,\n            size::SizeBoxSizeValue,\n            text::{TextBoxFont, TextBoxHorizontalAlign, TextBoxVerticalAlign},\n        },\n        utils::{Color, Rect},\n    },\n};\n\nconst DATA: &str = \"data\";\nconst INDEX: &str = \"index\";\n\nstruct AppData {\n    index: Managed<ViewModelValue<usize>>,\n}\n\nfn use_app(ctx: &mut WidgetContext) {\n    ctx.life_cycle.mount(|mut ctx| {\n        ctx.view_models\n            .bindings(DATA, INDEX)\n            .unwrap()\n            .bind(ctx.id.to_owned());\n    });\n}\n\n#[pre_hooks(use_nav_container_active, use_app)]\nfn app(mut ctx: WidgetContext) -> WidgetNode {\n    let idref = WidgetRef::default();\n    let mut app_data = ctx\n        .view_models\n        .view_model_mut(DATA)\n        .unwrap()\n        .write::<AppData>()\n        .unwrap();\n\n    make_widget!(content_box)\n        .idref(idref.clone())\n        .with_shared_props(PortalsContainer(idref))\n        .listed_slot(\n            make_widget!(options_view)\n                .with_props(ContentBoxItemLayout {\n                    anchors: 0.1.into(),\n                    margin: [-200.0, -40.0].into(),\n                    ..Default::default()\n                })\n                .with_props(OptionsViewProps {\n                    input: Some(app_data.index.lazy().into()),\n                })\n                .with_props(NavItemActive)\n                .with_props(PivotBoxProps {\n                    pivot: [0.0, 1.0].into(),\n                    align: 0.0.into(),\n                })\n                .with_props(SizeBoxProps {\n                    width: SizeBoxSizeValue::Exact(500.0),\n                    height: SizeBoxSizeValue::Exact(500.0),\n                    ..Default::default()\n                })\n                .named_slot(\n                    \"content\",\n                    make_widget!(content_box)\n                        .with_props(ContentBoxItemLayout {\n                            keep_in_bounds: true.into(),\n                            ..Default::default()\n                        })\n                        .listed_slot(make_widget!(image_box).with_props(ImageBoxProps::image(\n                            \"./crates/_/examples/resources/map.png\",\n                        ))),\n                )\n                .listed_slot(\n                    make_widget!(option)\n                        .with_props(\"Vidence\".to_owned())\n                        .with_props(NavItemActive)\n                        .with_props(marker_content_layout(0.1, 0.3)),\n                )\n                .listed_slot(\n                    make_widget!(option)\n                        .with_props(\"Yrale\".to_owned())\n                        .with_props(NavItemActive)\n                        .with_props(marker_content_layout(0.6, 0.2)),\n                )\n                .listed_slot(\n                    make_widget!(option)\n                        .with_props(\"Qock\".to_owned())\n                        .with_props(NavItemActive)\n                        .with_props(marker_content_layout(0.9, 0.6)),\n                )\n                .listed_slot(\n                    make_widget!(option)\n                        .with_props(\"Eryphia\".to_owned())\n                        .with_props(NavItemActive)\n                        .with_props(marker_content_layout(0.3, 0.7)),\n                ),\n        )\n        .into()\n}\n\nfn marker_content_layout(x: Scalar, y: Scalar) -> ContentBoxItemLayout {\n    ContentBoxItemLayout {\n        anchors: Rect {\n            left: x,\n            right: x,\n            top: y,\n            bottom: y,\n        },\n        margin: Rect {\n            left: -50.0,\n            right: -50.0,\n            top: -10.0,\n            bottom: -10.0,\n        },\n        align: 0.5.into(),\n        ..Default::default()\n    }\n}\n\nfn option(ctx: WidgetContext) -> WidgetNode {\n    match ctx.props.read_cloned_or_default::<OptionsViewMode>() {\n        OptionsViewMode::Selected => option_selected(ctx),\n        OptionsViewMode::Option => option_marker(ctx),\n    }\n}\n\nfn option_selected(ctx: WidgetContext) -> WidgetNode {\n    let ButtonProps {\n        selected, trigger, ..\n    } = ctx.props.read_cloned_or_default();\n    let color = if trigger {\n        Color {\n            r: 1.0,\n            g: 0.0,\n            b: 0.0,\n            a: 1.0,\n        }\n    } else if selected {\n        Color {\n            r: 0.0,\n            g: 0.0,\n            b: 1.0,\n            a: 1.0,\n        }\n    } else {\n        Color {\n            r: 0.0,\n            g: 0.0,\n            b: 0.0,\n            a: 1.0,\n        }\n    };\n    let text = ctx.props.read_cloned_or_default::<String>();\n\n    make_widget!(content_box)\n        .listed_slot(make_widget!(image_box).with_props(ImageBoxProps::colored(color)))\n        .listed_slot(make_widget!(text_box).with_props(TextBoxProps {\n            text,\n            font: TextBoxFont {\n                name: \"./demos/hello-world/resources/verdana.ttf\".to_owned(),\n                size: 32.0,\n            },\n            horizontal_align: TextBoxHorizontalAlign::Center,\n            vertical_align: TextBoxVerticalAlign::Middle,\n            color: Color {\n                r: 1.0,\n                g: 1.0,\n                b: 1.0,\n                a: 1.0,\n            },\n            ..Default::default()\n        }))\n        .into()\n}\n\nfn option_marker(ctx: WidgetContext) -> WidgetNode {\n    let ButtonProps {\n        selected, trigger, ..\n    } = ctx.props.read_cloned_or_default();\n    let color = if trigger {\n        Color {\n            r: 1.0,\n            g: 1.0,\n            b: 1.0,\n            a: 1.0,\n        }\n    } else if selected {\n        Color {\n            r: 0.5,\n            g: 0.5,\n            b: 0.5,\n            a: 1.0,\n        }\n    } else {\n        Color {\n            r: 0.0,\n            g: 0.0,\n            b: 0.0,\n            a: 1.0,\n        }\n    };\n    let text = ctx.props.read_cloned_or_default::<String>();\n\n    make_widget!(text_box)\n        .with_props(TextBoxProps {\n            text,\n            font: TextBoxFont {\n                name: \"./demos/hello-world/resources/verdana.ttf\".to_owned(),\n                size: 20.0,\n            },\n            horizontal_align: TextBoxHorizontalAlign::Center,\n            vertical_align: TextBoxVerticalAlign::Middle,\n            color,\n            ..Default::default()\n        })\n        .into()\n}\n\nfn main() {\n    let app = DeclarativeApp::default()\n        .tree(make_widget!(app))\n        .view_model(\n            DATA,\n            ViewModel::produce(|properties| AppData {\n                index: Managed::new(ViewModelValue::new(\n                    Default::default(),\n                    properties.notifier(INDEX),\n                )),\n            }),\n        );\n\n    App::new(AppConfig::default().title(\"Options View\")).run(app);\n}\n"
  },
  {
    "path": "crates/_/examples/portal_box.rs",
    "content": "// Make sure you have seen `anchor_box` code example first, because this is an evolution of that.\n\nuse raui_app::app::declarative::DeclarativeApp;\nuse raui_core::{\n    make_widget, pre_hooks,\n    widget::{\n        WidgetRef,\n        component::{\n            RelativeLayoutProps,\n            containers::{\n                anchor_box::{\n                    AnchorNotifyProps, AnchorProps, PivotBoxProps, anchor_box, pivot_box,\n                    use_anchor_box_notified_state,\n                },\n                content_box::content_box,\n                portal_box::{PortalsContainer, portal_box},\n            },\n            image_box::{ImageBoxProps, image_box},\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::{\n            content::ContentBoxItemLayout,\n            image::{ImageBoxColor, ImageBoxMaterial, ImageBoxSizeValue},\n        },\n        utils::Color,\n    },\n};\n\n// we use this hook that receives anchor box state change and store that in this component state.\n#[pre_hooks(use_anchor_box_notified_state)]\nfn app(mut ctx: WidgetContext) -> WidgetNode {\n    let idref = WidgetRef::default();\n\n    make_widget!(content_box)\n        .idref(idref.clone())\n        // widget rederence marked as portals container and put into root shared props for any\n        // portal box down the widget tree. More about how portal box works later.\n        .with_shared_props(PortalsContainer(idref.to_owned()))\n        .listed_slot(\n            make_widget!(anchor_box)\n                .with_props(RelativeLayoutProps {\n                    relative_to: idref.to_owned().into(),\n                })\n                // we make this anchor box notify this component about anchor box state change.\n                .with_props(AnchorNotifyProps(ctx.id.to_owned().into()))\n                .with_props(ContentBoxItemLayout {\n                    margin: 100.0.into(),\n                    ..Default::default()\n                })\n                .named_slot(\n                    \"content\",\n                    make_widget!(image_box).with_props(ImageBoxProps::colored(Color {\n                        r: 0.25,\n                        g: 0.25,\n                        b: 0.25,\n                        a: 1.0,\n                    })),\n                ),\n        )\n        .listed_slot(\n            // pivot box is used to calculate ContentBoxItemLayout that is later passed to its\n            // content so it works best with things like portal box which then uses that layout to\n            // position its content in portals container - in other words pivot box and portal box\n            // works best together.\n            make_widget!(pivot_box)\n                // pivot box uses AnchorProps for PivotBoxProps data to calculate a place to\n                // position the content relative to that area.\n                .with_props(ctx.state.read_cloned_or_default::<AnchorProps>())\n                .with_props(PivotBoxProps {\n                    // percentage of the anchored area to position at.\n                    pivot: 0.0.into(),\n                    // percentage of content area to align relative to pivot position.\n                    align: 0.75.into(),\n                })\n                .named_slot(\n                    \"content\",\n                    // portal box reads PortalsContainer from shared props and use its widget\n                    // reference to \"teleport\" portal box content into referenced container widget\n                    // (best container to use is content box) - what actually happen, RAUI sees\n                    // portal box, unwraps it, find referenced container and injects that unwrapped\n                    // content widget there.\n                    make_widget!(portal_box).named_slot(\n                        \"content\",\n                        make_widget!(image_box).with_props(ImageBoxProps {\n                            material: ImageBoxMaterial::Color(ImageBoxColor {\n                                color: Color {\n                                    r: 1.0,\n                                    g: 0.25,\n                                    b: 0.25,\n                                    a: 1.0,\n                                },\n                                ..Default::default()\n                            }),\n                            width: ImageBoxSizeValue::Exact(100.0),\n                            height: ImageBoxSizeValue::Exact(100.0),\n                            ..Default::default()\n                        }),\n                    ),\n                ),\n        )\n        .listed_slot(\n            make_widget!(pivot_box)\n                .with_props(ctx.state.read_cloned_or_default::<AnchorProps>())\n                .with_props(PivotBoxProps {\n                    pivot: 0.5.into(),\n                    align: 0.5.into(),\n                })\n                .named_slot(\n                    \"content\",\n                    make_widget!(portal_box).named_slot(\n                        \"content\",\n                        make_widget!(image_box).with_props(ImageBoxProps {\n                            material: ImageBoxMaterial::Color(ImageBoxColor {\n                                color: Color {\n                                    r: 0.25,\n                                    g: 1.0,\n                                    b: 0.25,\n                                    a: 1.0,\n                                },\n                                ..Default::default()\n                            }),\n                            width: ImageBoxSizeValue::Exact(200.0),\n                            height: ImageBoxSizeValue::Exact(200.0),\n                            ..Default::default()\n                        }),\n                    ),\n                ),\n        )\n        .listed_slot(\n            make_widget!(pivot_box)\n                .with_props(ctx.state.read_cloned_or_default::<AnchorProps>())\n                .with_props(PivotBoxProps {\n                    pivot: 1.0.into(),\n                    align: 0.25.into(),\n                })\n                .named_slot(\n                    \"content\",\n                    make_widget!(portal_box).named_slot(\n                        \"content\",\n                        make_widget!(image_box).with_props(ImageBoxProps {\n                            material: ImageBoxMaterial::Color(ImageBoxColor {\n                                color: Color {\n                                    r: 0.25,\n                                    g: 0.25,\n                                    b: 1.0,\n                                    a: 1.0,\n                                },\n                                ..Default::default()\n                            }),\n                            width: ImageBoxSizeValue::Exact(100.0),\n                            height: ImageBoxSizeValue::Exact(100.0),\n                            ..Default::default()\n                        }),\n                    ),\n                ),\n        )\n        .into()\n}\n\nfn main() {\n    DeclarativeApp::simple(\"Portal Box\", make_widget!(app));\n}\n"
  },
  {
    "path": "crates/_/examples/render_workers.rs",
    "content": "// This example shows how to render arbitrary geometry \"raw\" way into a texture\n// that can be used as image in the UI - useful for more demanding rendering.\n\nuse raui_app::{\n    Vertex,\n    app::declarative::DeclarativeApp,\n    render_worker::{RenderWorkerDescriptor, RenderWorkerTaskContext, RenderWorkersViewModel},\n    third_party::spitfire_glow::{\n        graphics::GraphicsBatch,\n        renderer::{GlowBlending, GlowTextureFormat, GlowUniformValue},\n    },\n};\nuse raui_core::{\n    make_widget, pre_hooks,\n    widget::{\n        component::image_box::{ImageBoxProps, image_box},\n        context::WidgetContext,\n        node::WidgetNode,\n    },\n};\n\nfn use_app(ctx: &mut WidgetContext) {\n    ctx.life_cycle.mount(|mut ctx| {\n        // RenderWorkersViewModel is a special view model that stores render worker\n        // surfaces that we can schedule render tasks to.\n        let mut workers = ctx\n            .view_models\n            .view_model_mut(RenderWorkersViewModel::VIEW_MODEL)\n            .unwrap()\n            .write::<RenderWorkersViewModel>()\n            .unwrap();\n        // First we add worker with the same id as the widget id, to ensure its\n        // uniqueness - we can use whatever id we want, but if workers are\n        // intended to be personalized to widgets, it's good to use widget id.\n        workers.add_worker(RenderWorkerDescriptor {\n            id: ctx.id.to_string(),\n            width: 256,\n            height: 256,\n            format: GlowTextureFormat::Rgba,\n            color: [1.0, 1.0, 1.0, 0.0],\n        });\n        // Once we added worker, we schedule to render its content first time.\n        workers.schedule_task(ctx.id.as_ref(), true, render_task);\n    });\n\n    ctx.life_cycle.unmount(|mut ctx| {\n        let mut workers = ctx\n            .view_models\n            .view_model_mut(RenderWorkersViewModel::VIEW_MODEL)\n            .unwrap()\n            .write::<RenderWorkersViewModel>()\n            .unwrap();\n        // When widget is unmounted, we need to remove the worker,\n        // otherwise it will be left in the view model for ever.\n        workers.remove_worker(ctx.id.as_ref());\n    });\n\n    ctx.life_cycle.change(|mut ctx| {\n        let mut workers = ctx\n            .view_models\n            .view_model_mut(RenderWorkersViewModel::VIEW_MODEL)\n            .unwrap()\n            .write::<RenderWorkersViewModel>()\n            .unwrap();\n        // When widget is changed, we need to update the worker surface content.\n        workers.schedule_task(ctx.id.as_ref(), true, render_task);\n    });\n}\n\n#[pre_hooks(use_app)]\nfn app(mut ctx: WidgetContext) -> WidgetNode {\n    // Show rendered worker surface as image with aspect ratio to not stretch it.\n    make_widget!(image_box)\n        .with_props(ImageBoxProps::image_aspect_ratio(ctx.id.as_ref(), false))\n        .into()\n}\n\nfn main() {\n    DeclarativeApp::simple(\"Render Workers\", make_widget!(app));\n}\n\n// Function representing render task that will paint some surface content.\nfn render_task(ctx: RenderWorkerTaskContext) {\n    ctx.graphics.state.stream.batch_optimized(GraphicsBatch {\n        shader: Some(ctx.colored_shader.clone()),\n        uniforms: [(\n            \"u_projection_view\".into(),\n            GlowUniformValue::M4(\n                ctx.graphics\n                    .state\n                    .main_camera\n                    .world_matrix()\n                    .into_col_array(),\n            ),\n        )]\n        .into_iter()\n        .collect(),\n        textures: Default::default(),\n        blending: GlowBlending::Alpha,\n        scissor: None,\n        wireframe: false,\n    });\n    ctx.graphics.state.stream.quad([\n        Vertex {\n            position: [\n                ctx.graphics.state.main_camera.screen_size.x * 0.25,\n                ctx.graphics.state.main_camera.screen_size.y * 0.25,\n            ],\n            uv: [0.0, 0.0, 0.0],\n            color: [1.0, 0.0, 0.0, 1.0],\n        },\n        Vertex {\n            position: [\n                ctx.graphics.state.main_camera.screen_size.x * 0.75,\n                ctx.graphics.state.main_camera.screen_size.y * 0.25,\n            ],\n            uv: [0.0, 0.0, 0.0],\n            color: [0.0, 1.0, 0.0, 1.0],\n        },\n        Vertex {\n            position: [\n                ctx.graphics.state.main_camera.screen_size.x * 0.75,\n                ctx.graphics.state.main_camera.screen_size.y * 0.75,\n            ],\n            uv: [0.0, 0.0, 0.0],\n            color: [0.0, 0.0, 1.0, 1.0],\n        },\n        Vertex {\n            position: [\n                ctx.graphics.state.main_camera.screen_size.x * 0.25,\n                ctx.graphics.state.main_camera.screen_size.y * 0.75,\n            ],\n            uv: [0.0, 0.0, 0.0],\n            color: [1.0, 1.0, 0.0, 1.0],\n        },\n    ]);\n}\n"
  },
  {
    "path": "crates/_/examples/resources/long_text.txt",
    "content": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
  },
  {
    "path": "crates/_/examples/responsive_box.rs",
    "content": "use raui_app::app::declarative::DeclarativeApp;\nuse raui_core::{\n    make_widget,\n    widget::{\n        component::{\n            containers::{\n                content_box::content_box,\n                responsive_box::{MediaQueryExpression, MediaQueryOrientation, responsive_box},\n            },\n            image_box::{ImageBoxProps, image_box},\n            text_box::{TextBoxProps, text_box},\n        },\n        unit::text::{TextBoxFont, TextBoxHorizontalAlign, TextBoxVerticalAlign},\n        utils::Color,\n    },\n};\n\nfn main() {\n    let tree = make_widget!(content_box)\n        .listed_slot(\n            // responsive box allows to select one of listed slot widgets to\n            // present, depending on which slot widget's media query expression\n            // passes. ordering of listed slots is important, the first one that\n            // passes will be used. media query expressions can be combined with\n            // logical operator expressions such as `and`, `or` and `not`.\n            // in case of default case, use `any` expression.\n            make_widget!(responsive_box)\n                .listed_slot(\n                    make_widget!(image_box)\n                        .key(\"landscape\")\n                        .with_props(MediaQueryExpression::ScreenOrientation(\n                            MediaQueryOrientation::Landscape,\n                        ))\n                        .with_props(ImageBoxProps::colored(Color {\n                            r: 0.25,\n                            g: 1.0,\n                            b: 0.25,\n                            a: 1.0,\n                        })),\n                )\n                .listed_slot(make_widget!(image_box).key(\"portrait\").with_props(\n                    ImageBoxProps::colored(Color {\n                        r: 0.25,\n                        g: 0.25,\n                        b: 1.0,\n                        a: 1.0,\n                    }),\n                )),\n        )\n        .listed_slot(make_widget!(text_box).with_props(TextBoxProps {\n            text: \"Change window size to observe responsiveness\".to_owned(),\n            font: TextBoxFont {\n                name: \"./demos/hello-world/resources/verdana.ttf\".to_owned(),\n                size: 64.0,\n            },\n            color: Color {\n                r: 0.25,\n                g: 0.0,\n                b: 0.0,\n                a: 1.0,\n            },\n            horizontal_align: TextBoxHorizontalAlign::Center,\n            vertical_align: TextBoxVerticalAlign::Middle,\n            ..Default::default()\n        }));\n\n    DeclarativeApp::simple(\"Responsive Box\", tree);\n}\n"
  },
  {
    "path": "crates/_/examples/responsive_props_box.rs",
    "content": "// Make sure you have seen `responsive_box` code example first, because this is an evolution of that.\n\nuse raui_app::app::declarative::DeclarativeApp;\nuse raui_core::{\n    make_widget,\n    widget::{\n        component::{\n            containers::{\n                content_box::content_box,\n                responsive_box::{\n                    MediaQueryExpression, MediaQueryOrientation, responsive_props_box,\n                },\n            },\n            image_box::{ImageBoxProps, image_box},\n            text_box::{TextBoxProps, text_box},\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        none_widget,\n        unit::text::{TextBoxFont, TextBoxHorizontalAlign, TextBoxVerticalAlign},\n        utils::Color,\n    },\n};\n\nfn widget(context: WidgetContext) -> WidgetNode {\n    let WidgetContext { key, props, .. } = context;\n\n    let landscape = props.read_cloned_or_default::<bool>();\n    let color = if landscape {\n        Color {\n            r: 0.25,\n            g: 1.0,\n            b: 0.25,\n            a: 1.0,\n        }\n    } else {\n        Color {\n            r: 0.25,\n            g: 0.25,\n            b: 1.0,\n            a: 1.0,\n        }\n    };\n    let text = if landscape {\n        \"Landscape\".to_owned()\n    } else {\n        \"Portrait\".to_owned()\n    };\n\n    make_widget!(content_box)\n        .key(key)\n        .listed_slot(\n            make_widget!(image_box)\n                .key(\"image\")\n                .with_props(ImageBoxProps::colored(color)),\n        )\n        .listed_slot(make_widget!(text_box).with_props(TextBoxProps {\n            text,\n            font: TextBoxFont {\n                name: \"./demos/hello-world/resources/verdana.ttf\".to_owned(),\n                size: 64.0,\n            },\n            color: Color {\n                r: 0.25,\n                g: 0.0,\n                b: 0.0,\n                a: 1.0,\n            },\n            horizontal_align: TextBoxHorizontalAlign::Center,\n            vertical_align: TextBoxVerticalAlign::Middle,\n            ..Default::default()\n        }))\n        .into()\n}\n\nfn main() {\n    // responsive props box allows to select listed slot with media query, but\n    // instead of selecting that slot as content, it only grabs its props and\n    // applies them to named `content` slot - this is quite useful if we have\n    // single kind of widget we wanna present, but its props are what's different.\n    let tree = make_widget!(responsive_props_box)\n        .listed_slot(\n            // since because slot widget is not used, we need use `none_widget`\n            // to not pollute UI with complex widgets that won't be ever used.\n            make_widget!(none_widget)\n                .with_props(MediaQueryExpression::ScreenOrientation(\n                    MediaQueryOrientation::Portrait,\n                ))\n                .with_props(false),\n        )\n        .listed_slot(make_widget!(none_widget).with_props(true))\n        .named_slot(\"content\", make_widget!(widget));\n\n    DeclarativeApp::simple(\"Responsive Props Box\", tree);\n}\n"
  },
  {
    "path": "crates/_/examples/retained_mode.rs",
    "content": "// Example of retained mode UI on top of RAUI.\n// It's goals are very similar to Unreal's UMG on top of Slate.\n// Evolution of this approach allows to use retained mode views\n// within declarative mode widgets and vice versa - they\n// interleave quite seamingly.\n\nuse std::any::Any;\n\nuse raui_app::app::retained::RetainedApp;\nuse raui_core::{\n    application::ChangeNotifier,\n    make_widget,\n    widget::{\n        component::{\n            containers::{\n                content_box::content_box,\n                horizontal_box::{HorizontalBoxProps, horizontal_box},\n                vertical_box::{VerticalBoxProps, vertical_box},\n            },\n            image_box::{ImageBoxProps, image_box},\n            interactive::{\n                button::{ButtonNotifyMessage, ButtonNotifyProps, button},\n                navigation::{NavItemActive, use_nav_container_active},\n            },\n            text_box::{TextBoxProps, text_box},\n        },\n        context::{WidgetContext, WidgetMountOrChangeContext},\n        node::WidgetNode,\n        unit::{flex::FlexBoxItemLayout, text::TextBoxFont},\n        utils::Color,\n    },\n};\nuse raui_retained::{View, ViewState, ViewValue};\n\nconst FONT: &str = \"./demos/hello-world/resources/verdana.ttf\";\n\n// root view of an application.\nstruct AppView {\n    pub counter: View<CounterView>,\n    pub increment_button: View<Button<LabelView>>,\n    pub decrement_button: View<Button<LabelView>>,\n}\n\nimpl ViewState for AppView {\n    // `on_render` method constructs declarative nodes out of\n    // retained node. this is similar to how Unreal's UMG builds\n    // Slate widgets tree. you can do here whatever you would do\n    // normally in RAUI widget component functions.\n    fn on_render(&self, mut context: WidgetContext) -> WidgetNode {\n        // as usual, at least root view should produce navigable\n        // container to enable navigation on the UI, here navigation\n        // being button clicks.\n        context.use_hook(use_nav_container_active);\n\n        make_widget!(vertical_box)\n            .with_props(VerticalBoxProps {\n                override_slots_layout: Some(FlexBoxItemLayout {\n                    basis: Some(48.0),\n                    grow: 0.0,\n                    shrink: 0.0,\n                    ..Default::default()\n                }),\n                ..Default::default()\n            })\n            .listed_slot(self.counter.component().key(\"counter\"))\n            .listed_slot(\n                make_widget!(horizontal_box)\n                    .with_props(HorizontalBoxProps {\n                        separation: 50.0,\n                        ..Default::default()\n                    })\n                    .listed_slot(self.increment_button.component().key(\"increment\"))\n                    .listed_slot(self.decrement_button.component().key(\"decrement\")),\n            )\n            .into()\n    }\n\n    fn as_any(&self) -> &dyn Any {\n        self\n    }\n\n    fn as_any_mut(&mut self) -> &mut dyn Any {\n        self\n    }\n}\n\n// counter view stores value that can notify RAUI about this\n// widget being changed, so it will get re-renderred.\n// if we don't wrap data that has to be observed in `ViewValue`,\n// then we would need to find other way to notify RAUI app\n// about the change in data whenever it happen, usually manually.\n// alternatively we could use View-Model feature as we would\n// normally do with RAUI, if we don't want to store host data in\n// views (which is always good approach to take).\nstruct CounterView {\n    pub counter: ViewValue<usize>,\n}\n\nimpl ViewState for CounterView {\n    fn on_render(&self, _: WidgetContext) -> WidgetNode {\n        make_widget!(text_box)\n            // to allow `ViewValue` notify RAUI app about changes,\n            // we need to pass its widget ref to RAUI component.\n            // `ViewValue` pass that widget id to change notifications.\n            .idref(self.counter.widget_ref())\n            .with_props(TextBoxProps {\n                text: self.counter.to_string(),\n                font: TextBoxFont {\n                    name: FONT.to_owned(),\n                    size: 32.0,\n                },\n                color: Color {\n                    r: 0.0,\n                    g: 0.0,\n                    b: 0.0,\n                    a: 1.0,\n                },\n                ..Default::default()\n            })\n            .into()\n    }\n\n    fn as_any(&self) -> &dyn Any {\n        self\n    }\n\n    fn as_any_mut(&mut self) -> &mut dyn Any {\n        self\n    }\n}\n\nstruct LabelView {\n    pub text: String,\n}\n\nimpl LabelView {\n    fn new(text: impl ToString) -> Self {\n        Self {\n            text: text.to_string(),\n        }\n    }\n}\n\nimpl ViewState for LabelView {\n    fn on_render(&self, _: WidgetContext) -> WidgetNode {\n        make_widget!(text_box)\n            .with_props(TextBoxProps {\n                text: self.text.to_owned(),\n                font: TextBoxFont {\n                    name: FONT.to_owned(),\n                    size: 32.0,\n                },\n                color: Color {\n                    r: 0.0,\n                    g: 0.0,\n                    b: 0.0,\n                    a: 1.0,\n                },\n                ..Default::default()\n            })\n            .into()\n    }\n\n    fn as_any(&self) -> &dyn Any {\n        self\n    }\n\n    fn as_any_mut(&mut self) -> &mut dyn Any {\n        self\n    }\n}\n\n// button that can store `on click` callback that\n// gets called whenever RAUI button detects click.\nstruct Button<T: ViewState> {\n    pub content: View<T>,\n    on_click: Option<Box<dyn Fn() + Send + Sync>>,\n}\n\nimpl<T: ViewState> Button<T> {\n    fn new(content: View<T>) -> Self {\n        Self {\n            content,\n            on_click: None,\n        }\n    }\n\n    fn on_click(mut self, on_click: impl Fn() + Send + Sync + 'static) -> Self {\n        self.on_click = Some(Box::new(on_click));\n        self\n    }\n}\n\nimpl<T: ViewState> ViewState for Button<T> {\n    fn on_change(&mut self, context: WidgetMountOrChangeContext) {\n        // as usual, we listen for button messages sent to this\n        // widget and call stored callback.\n        if let Some(on_click) = self.on_click.take() {\n            for message in context.messenger.messages {\n                if let Some(message) = message.as_any().downcast_ref::<ButtonNotifyMessage>()\n                    && message.trigger_start()\n                {\n                    on_click();\n                }\n            }\n            self.on_click = Some(on_click);\n        }\n    }\n\n    fn on_render(&self, context: WidgetContext) -> WidgetNode {\n        make_widget!(button)\n            .with_props(NavItemActive)\n            // this enables RAUI interaction system to send button\n            // events to same widget\n            .with_props(ButtonNotifyProps(context.id.to_owned().into()))\n            .named_slot(\n                \"content\",\n                make_widget!(content_box)\n                    .listed_slot(make_widget!(image_box).with_props(ImageBoxProps::colored(\n                        Color {\n                            r: 0.75,\n                            g: 0.75,\n                            b: 0.75,\n                            a: 1.0,\n                        },\n                    )))\n                    .listed_slot(self.content.component()),\n            )\n            .into()\n    }\n\n    fn as_any(&self) -> &dyn Any {\n        self\n    }\n\n    fn as_any_mut(&mut self) -> &mut dyn Any {\n        self\n    }\n}\n\n// here we construct application view tree out of view objects.\nfn create_app(notifier: ChangeNotifier) -> View<AppView> {\n    // create counter view and get lazy handles to it for buttons to use.\n    // nice thing about lazy views is that they can be shared across\n    // entire application - think of them just as handles/references to\n    // any view you create, that can be accessed from whatever place.\n    let counter = View::new(CounterView {\n        counter: ViewValue::new(0).with_notifier(notifier),\n    });\n    let lazy_counter_increment = counter.lazy();\n    let lazy_counter_decrement = counter.lazy();\n\n    let increment_button = View::new(Button::new(View::new(LabelView::new(\"Add\"))).on_click(\n        move || {\n            // we can access other views using lazy views.\n            *lazy_counter_increment.write().unwrap().counter += 1;\n        },\n    ));\n    let decrement_button = View::new(Button::new(View::new(LabelView::new(\"Subtract\"))).on_click(\n        move || {\n            let mut access = lazy_counter_decrement.write().unwrap();\n            *access.counter = access.counter.saturating_sub(1);\n        },\n    ));\n\n    View::new(AppView {\n        counter,\n        increment_button,\n        decrement_button,\n    })\n}\n\nfn main() {\n    RetainedApp::simple(\"Retained mode UI\", create_app);\n}\n"
  },
  {
    "path": "crates/_/examples/scroll_box.rs",
    "content": "use raui_app::app::declarative::DeclarativeApp;\nuse raui_core::{\n    make_widget, pre_hooks,\n    widget::{\n        component::{\n            containers::{\n                scroll_box::{SideScrollbarsProps, nav_scroll_box, nav_scroll_box_side_scrollbars},\n                size_box::{SizeBoxProps, size_box},\n                vertical_box::{VerticalBoxProps, vertical_box},\n                wrap_box::{WrapBoxProps, wrap_box},\n            },\n            image_box::{ImageBoxProps, image_box},\n            interactive::{\n                button::{ButtonNotifyMessage, ButtonNotifyProps, button},\n                navigation::{NavItemActive, use_nav_container_active},\n                scroll_view::ScrollViewRange,\n            },\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::{\n            flex::FlexBoxItemLayout,\n            image::{ImageBoxColor, ImageBoxMaterial},\n            size::SizeBoxSizeValue,\n        },\n        utils::{Color, Rect},\n    },\n};\n\n// we make this root widget a navigable container to let scrol box perform scrolling.\n#[pre_hooks(use_nav_container_active)]\nfn app(mut ctx: WidgetContext) -> WidgetNode {\n    make_widget!(wrap_box)\n        .with_props(WrapBoxProps {\n            margin: Rect {\n                left: 50.0,\n                right: 50.0,\n                top: 75.0,\n                bottom: 25.0,\n            },\n            ..Default::default()\n        })\n        .named_slot(\n            \"content\",\n            make_widget!(nav_scroll_box)\n                // we activate scroll box navigation - it is disabled by default.\n                .with_props(NavItemActive)\n                // apply scroll view range to limit scrolling area (without it you could scroll infinitely).\n                .with_props(ScrollViewRange::default())\n                .named_slot(\n                    \"content\",\n                    // typical use of scroll box is to wrap around some kind of list but we can actually\n                    // put there anything and scroll box will scroll that content.\n                    make_widget!(vertical_box)\n                        .with_props(VerticalBoxProps {\n                            override_slots_layout: Some(FlexBoxItemLayout {\n                                grow: 0.0,\n                                shrink: 0.0,\n                                ..Default::default()\n                            }),\n                            ..Default::default()\n                        })\n                        .listed_slot(make_widget!(item).key(0).with_props(true))\n                        .listed_slot(make_widget!(item).key(1).with_props(false))\n                        .listed_slot(make_widget!(item).key(2).with_props(true))\n                        .listed_slot(make_widget!(item).key(3).with_props(false))\n                        .listed_slot(make_widget!(item).key(4).with_props(true))\n                        .listed_slot(make_widget!(item).key(5).with_props(false)),\n                )\n                .named_slot(\n                    \"scrollbars\",\n                    // scrollbars used here are side buttons that you can drag to scroll content on\n                    // separate axes, but you could make a custom scrollbars component that for example\n                    // uses single button that allows to scroll in both axes at once with dragging.\n                    make_widget!(nav_scroll_box_side_scrollbars).with_props(SideScrollbarsProps {\n                        size: 20.0,\n                        back_material: Some(ImageBoxMaterial::Color(ImageBoxColor {\n                            color: Color {\n                                r: 0.15,\n                                g: 0.15,\n                                b: 0.15,\n                                a: 1.0,\n                            },\n                            ..Default::default()\n                        })),\n                        front_material: ImageBoxMaterial::Color(ImageBoxColor {\n                            color: Color {\n                                r: 0.85,\n                                g: 0.85,\n                                b: 0.85,\n                                a: 1.0,\n                            },\n                            ..Default::default()\n                        }),\n                    }),\n                ),\n        )\n        .into()\n}\n\nfn use_item(ctx: &mut WidgetContext) {\n    ctx.life_cycle.change(|ctx| {\n        for msg in ctx.messenger.messages {\n            if let Some(msg) = msg.as_any().downcast_ref::<ButtonNotifyMessage>()\n                && msg.trigger_start()\n            {\n                println!(\"Button clicked: {:?}\", msg.sender.key());\n            }\n        }\n    });\n}\n\n#[pre_hooks(use_item)]\nfn item(mut ctx: WidgetContext) -> WidgetNode {\n    let color = if ctx.props.read_cloned_or_default::<bool>() {\n        Color {\n            r: 0.5,\n            g: 0.5,\n            b: 0.5,\n            a: 1.0,\n        }\n    } else {\n        Color {\n            r: 0.25,\n            g: 0.25,\n            b: 0.25,\n            a: 1.0,\n        }\n    };\n\n    make_widget!(button)\n        .with_props(NavItemActive)\n        .with_props(ButtonNotifyProps(ctx.id.to_owned().into()))\n        .named_slot(\n            \"content\",\n            make_widget!(size_box)\n                .with_props(SizeBoxProps {\n                    width: SizeBoxSizeValue::Fill,\n                    height: SizeBoxSizeValue::Exact(136.0),\n                    ..Default::default()\n                })\n                .named_slot(\n                    \"content\",\n                    make_widget!(image_box).with_props(ImageBoxProps::colored(color)),\n                ),\n        )\n        .into()\n}\n\nfn main() {\n    DeclarativeApp::simple(\"Scroll Box\", make_widget!(app));\n}\n"
  },
  {
    "path": "crates/_/examples/scroll_box_adaptive.rs",
    "content": "use raui_app::app::declarative::DeclarativeApp;\nuse raui_core::{\n    make_widget, pre_hooks,\n    widget::{\n        component::{\n            containers::{\n                scroll_box::{SideScrollbarsProps, nav_scroll_box, nav_scroll_box_side_scrollbars},\n                size_box::{SizeBoxProps, size_box},\n                vertical_box::{VerticalBoxProps, vertical_box},\n                wrap_box::{WrapBoxProps, wrap_box},\n            },\n            image_box::{ImageBoxProps, image_box},\n            interactive::{\n                navigation::{NavItemActive, use_nav_container_active},\n                scroll_view::ScrollViewRange,\n            },\n            text_box::{TextBoxProps, text_box},\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::{\n            flex::FlexBoxItemLayout,\n            image::{\n                ImageBoxAspectRatio, ImageBoxColor, ImageBoxImage, ImageBoxMaterial,\n                ImageBoxSizeValue,\n            },\n            size::SizeBoxSizeValue,\n            text::{TextBoxFont, TextBoxSizeValue},\n        },\n        utils::{Color, Rect},\n    },\n};\n\n#[pre_hooks(use_nav_container_active)]\nfn app(mut ctx: WidgetContext) -> WidgetNode {\n    make_widget!(wrap_box)\n        .with_props(WrapBoxProps {\n            margin: Rect {\n                left: 100.0,\n                right: 50.0,\n                top: 75.0,\n                bottom: 25.0,\n            },\n            ..Default::default()\n        })\n        .named_slot(\n            \"content\",\n            make_widget!(nav_scroll_box)\n                .with_props(NavItemActive)\n                .with_props(ScrollViewRange::default())\n                .named_slot(\n                    \"content\",\n                    make_widget!(size_box)\n                        .with_props(SizeBoxProps {\n                            width: SizeBoxSizeValue::Fill,\n                            // first we make sure to put content in size box that\n                            // uses content height to make it take minimal space.\n                            height: SizeBoxSizeValue::Content,\n                            ..Default::default()\n                        })\n                        .named_slot(\n                            \"content\",\n                            make_widget!(vertical_box)\n                                .with_props(VerticalBoxProps {\n                                    // we need to make sure all items are not\n                                    // growing and shrinking to let size box\n                                    // calculate correct content size. growing\n                                    // and shrinking would make items take all\n                                    // available space, filling all container.\n                                    override_slots_layout: Some(\n                                        FlexBoxItemLayout::no_growing_and_shrinking(),\n                                    ),\n                                    ..Default::default()\n                                })\n                                .listed_slot(make_widget!(image_box).with_props(ImageBoxProps {\n                                    height: ImageBoxSizeValue::Exact(300.0),\n                                    material: ImageBoxMaterial::Image(ImageBoxImage {\n                                        id: \"./crates/_/examples/resources/map.png\".to_owned(),\n                                        ..Default::default()\n                                    }),\n                                    content_keep_aspect_ratio: Some(ImageBoxAspectRatio {\n                                        horizontal_alignment: 0.5,\n                                        vertical_alignment: 0.5,\n                                        outside: false,\n                                    }),\n                                    ..Default::default()\n                                }))\n                                .listed_slot(make_widget!(text_box).with_props(TextBoxProps {\n                                    text: include_str!(\"./resources/long_text.txt\").to_owned(),\n                                    font: TextBoxFont {\n                                        name:\n                                            \"./demos/hello-world/resources/verdana.ttf\".to_owned(),\n                                        size: 64.0,\n                                    },\n                                    color: Color {\n                                        r: 0.0,\n                                        g: 0.0,\n                                        b: 0.5,\n                                        a: 1.0,\n                                    },\n                                    height: TextBoxSizeValue::Content,\n                                    ..Default::default()\n                                })),\n                        ),\n                )\n                .named_slot(\n                    \"scrollbars\",\n                    make_widget!(nav_scroll_box_side_scrollbars).with_props(SideScrollbarsProps {\n                        size: 20.0,\n                        back_material: Some(ImageBoxMaterial::Color(ImageBoxColor {\n                            color: Color {\n                                r: 0.15,\n                                g: 0.15,\n                                b: 0.15,\n                                a: 1.0,\n                            },\n                            ..Default::default()\n                        })),\n                        front_material: ImageBoxMaterial::Color(ImageBoxColor {\n                            color: Color {\n                                r: 0.85,\n                                g: 0.85,\n                                b: 0.85,\n                                a: 1.0,\n                            },\n                            ..Default::default()\n                        }),\n                    }),\n                ),\n        )\n        .into()\n}\n\nfn main() {\n    DeclarativeApp::simple(\"Scroll Box - Adaptive content size\", make_widget!(app));\n}\n"
  },
  {
    "path": "crates/_/examples/setup.rs",
    "content": "use raui_core::{\n    application::Application,\n    interactive::default_interactions_engine::{\n        DefaultInteractionsEngine, Interaction, PointerButton,\n    },\n    layout::{CoordsMapping, default_layout_engine::DefaultLayoutEngine},\n    make_widget,\n    widget::{\n        component::{\n            containers::content_box::nav_content_box,\n            image_box::image_box,\n            interactive::{button::button, navigation::NavItemActive},\n        },\n        setup,\n        utils::{Rect, Vec2},\n    },\n};\nuse raui_json_renderer::JsonRenderer;\n\nfn main() {\n    // Create the application\n    let mut application = Application::default();\n\n    // We need to run the \"setup\" functions for the application to register components and\n    // properties if we want to support serialization of the UI. We pass it a function that\n    // will do the actual registration\n    application.setup(setup);\n    // application.setup(raui_material::setup /* and the raui_material setup if we need it */);\n\n    // Create the renderer. In this case we render the UI to JSON for simplicity, but usually\n    // you would have a custom renderer for your game engine or renderer.\n    let mut renderer = JsonRenderer { pretty: true };\n\n    // Create the interactions engine. The default interactions engine covers typical\n    // pointer + keyboard + gamepad navigation/interactions.\n    let mut interactions = DefaultInteractionsEngine::default();\n\n    // We create our widget tree\n    let tree = make_widget!(nav_content_box).key(\"app\").listed_slot(\n        make_widget!(button)\n            .key(\"button\")\n            .with_props(NavItemActive)\n            .named_slot(\"content\", make_widget!(image_box).key(\"icon\")),\n    );\n\n    // We apply the tree to the application. This must be done again if we wish to change the\n    // tree.\n    application.apply(tree);\n\n    // This scope content would need to be called every frame\n    {\n        // Telling the app to `process` will make it perform any necessary updates.\n        application.process();\n\n        // To properly handle layout we need to create a mapping of the screen coordinates to\n        // the RAUI coordinates. We would update this with the size of the window every frame.\n        let mapping = CoordsMapping::new(Rect {\n            left: 0.0,\n            right: 1024.0,\n            top: 0.0,\n            bottom: 576.0,\n        });\n\n        // we interact with UI by sending interaction messages to the engine. You would hook this up\n        // to whatever game engine or window event loop to perform the proper interactions when\n        // different events are emitted.\n        interactions.interact(Interaction::PointerMove(Vec2 { x: 200.0, y: 100.0 }));\n        interactions.interact(Interaction::PointerDown(\n            PointerButton::Trigger,\n            Vec2 { x: 200.0, y: 100.0 },\n        ));\n\n        // We apply the application layout.\n        // We use the default layout engine, but you could make your own layout engine.\n        let mut layout_engine = DefaultLayoutEngine::<()>::default();\n        application.layout(&mapping, &mut layout_engine).unwrap();\n\n        // Since interactions engines require constructed layout to process interactions we\n        // have to process interactions after we layout the UI.\n        application.interact(&mut interactions).unwrap();\n\n        // Now we render the app\n        println!(\n            \"{}\",\n            application\n                .render::<_, String, _>(&mapping, &mut renderer)\n                .unwrap()\n        );\n    }\n}\n"
  },
  {
    "path": "crates/_/examples/size_box.rs",
    "content": "use raui_app::app::declarative::DeclarativeApp;\nuse raui_core::{\n    make_widget,\n    widget::{\n        component::{\n            containers::size_box::{SizeBoxProps, size_box},\n            image_box::{ImageBoxProps, image_box},\n        },\n        unit::size::SizeBoxSizeValue,\n        utils::Color,\n    },\n};\n\nfn main() {\n    let tree = make_widget!(size_box)\n        .with_props(SizeBoxProps {\n            // takes the layout box size from its children size. content size is the default one.\n            width: SizeBoxSizeValue::Content,\n            height: SizeBoxSizeValue::Content,\n            ..Default::default()\n        })\n        .named_slot(\n            \"content\",\n            make_widget!(size_box)\n                .with_props(SizeBoxProps {\n                    // exact size resets layout available size into size defined here.\n                    // it simply ignores available size and uses this one down the widget tree.\n                    width: SizeBoxSizeValue::Exact(400.0),\n                    height: SizeBoxSizeValue::Exact(300.0),\n                    ..Default::default()\n                })\n                .named_slot(\n                    \"content\",\n                    make_widget!(size_box)\n                        .with_props(SizeBoxProps {\n                            // uses layout available size defined by this widget parent node.\n                            width: SizeBoxSizeValue::Fill,\n                            height: SizeBoxSizeValue::Fill,\n                            // we can additionally set margin.\n                            margin: 50.0.into(),\n                            ..Default::default()\n                        })\n                        .named_slot(\n                            \"content\",\n                            make_widget!(image_box).with_props(ImageBoxProps::colored(Color {\n                                r: 1.0,\n                                g: 0.25,\n                                b: 0.25,\n                                a: 1.0,\n                            })),\n                        ),\n                ),\n        );\n\n    DeclarativeApp::simple(\"Size Box\", tree);\n}\n"
  },
  {
    "path": "crates/_/examples/size_box_aspect_ratio.rs",
    "content": "// Make sure you have seen `size_box` code example first, because this is an evolution of that.\n\nuse raui_app::app::declarative::DeclarativeApp;\nuse raui_core::{\n    make_widget,\n    widget::{\n        component::{\n            containers::size_box::{SizeBoxProps, size_box},\n            image_box::{ImageBoxProps, image_box},\n        },\n        unit::size::{SizeBoxAspectRatio, SizeBoxSizeValue},\n        utils::Color,\n    },\n};\n\nfn main() {\n    let tree = make_widget!(size_box)\n        .with_props(SizeBoxProps {\n            width: SizeBoxSizeValue::Fill,\n            height: SizeBoxSizeValue::Fill,\n            // enforce width to be percentage of height.\n            keep_aspect_ratio: SizeBoxAspectRatio::WidthOfHeight(0.5),\n            ..Default::default()\n        })\n        .named_slot(\n            \"content\",\n            make_widget!(image_box).with_props(ImageBoxProps::colored(Color {\n                r: 1.0,\n                g: 0.25,\n                b: 0.25,\n                a: 1.0,\n            })),\n        );\n\n    DeclarativeApp::simple(\"Size Box - Keep Aspect Ratio\", tree);\n}\n"
  },
  {
    "path": "crates/_/examples/slider_view.rs",
    "content": "use raui_app::app::{App, AppConfig, declarative::DeclarativeApp};\nuse raui_core::{\n    Managed, make_widget, pre_hooks,\n    view_model::{ViewModel, ViewModelValue},\n    widget::{\n        component::{\n            containers::{\n                content_box::content_box,\n                horizontal_box::horizontal_box,\n                vertical_box::{VerticalBoxProps, vertical_box},\n            },\n            image_box::{ImageBoxProps, image_box},\n            interactive::{\n                navigation::{NavItemActive, use_nav_container_active},\n                slider_view::{SliderViewDirection, SliderViewProps, slider_view},\n            },\n            text_box::{TextBoxProps, text_box},\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::{\n            content::ContentBoxItemLayout,\n            flex::FlexBoxItemLayout,\n            text::{TextBoxFont, TextBoxHorizontalAlign, TextBoxVerticalAlign},\n        },\n        utils::{Color, Rect},\n    },\n};\n\nconst DATA: &str = \"data\";\nconst FLOAT_INPUT: &str = \"float-input\";\nconst INTEGER_INPUT: &str = \"integer-input\";\nconst UNSIGNED_INTEGER_INPUT: &str = \"unsigned-integer-input\";\n\nstruct AppData {\n    float_input: Managed<ViewModelValue<f32>>,\n    integer_input: Managed<ViewModelValue<i32>>,\n    unsigned_integer_input: Managed<ViewModelValue<u8>>,\n}\n\nfn use_app(ctx: &mut WidgetContext) {\n    ctx.life_cycle.mount(|mut ctx| {\n        ctx.view_models\n            .bindings(DATA, FLOAT_INPUT)\n            .unwrap()\n            .bind(ctx.id.to_owned());\n        ctx.view_models\n            .bindings(DATA, INTEGER_INPUT)\n            .unwrap()\n            .bind(ctx.id.to_owned());\n        ctx.view_models\n            .bindings(DATA, UNSIGNED_INTEGER_INPUT)\n            .unwrap()\n            .bind(ctx.id.to_owned());\n    });\n}\n\n#[pre_hooks(use_nav_container_active, use_app)]\nfn app(mut ctx: WidgetContext) -> WidgetNode {\n    let mut app_data = ctx\n        .view_models\n        .view_model_mut(DATA)\n        .unwrap()\n        .write::<AppData>()\n        .unwrap();\n\n    make_widget!(horizontal_box)\n        .listed_slot(\n            make_widget!(input)\n                .with_props(FlexBoxItemLayout {\n                    margin: 50.0.into(),\n                    ..Default::default()\n                })\n                .with_props(SliderViewProps {\n                    input: Some(app_data.float_input.lazy().into()),\n                    from: -10.0,\n                    to: 10.0,\n                    direction: SliderViewDirection::BottomToTop,\n                }),\n        )\n        .listed_slot(\n            make_widget!(vertical_box)\n                .with_props(VerticalBoxProps {\n                    override_slots_layout: Some(FlexBoxItemLayout {\n                        margin: 50.0.into(),\n                        ..Default::default()\n                    }),\n                    ..Default::default()\n                })\n                .listed_slot(make_widget!(input).with_props(SliderViewProps {\n                    input: Some(app_data.integer_input.lazy().into()),\n                    from: -2.0,\n                    to: 2.0,\n                    ..Default::default()\n                }))\n                .listed_slot(make_widget!(input).with_props(SliderViewProps {\n                    input: Some(app_data.unsigned_integer_input.lazy().into()),\n                    from: -3.0,\n                    to: 7.0,\n                    direction: SliderViewDirection::RightToLeft,\n                })),\n        )\n        .into()\n}\n\nfn input(ctx: WidgetContext) -> WidgetNode {\n    let props = ctx.props.read_cloned_or_default::<SliderViewProps>();\n    let percentage = props.get_percentage();\n    let value = props.get_value();\n    let anchors = match props.direction {\n        SliderViewDirection::LeftToRight => Rect {\n            left: 0.0,\n            right: percentage,\n            top: 0.0,\n            bottom: 1.0,\n        },\n        SliderViewDirection::RightToLeft => Rect {\n            left: 1.0 - percentage,\n            right: 1.0,\n            top: 0.0,\n            bottom: 1.0,\n        },\n        SliderViewDirection::TopToBottom => Rect {\n            left: 0.0,\n            right: 1.0,\n            top: 0.0,\n            bottom: percentage,\n        },\n        SliderViewDirection::BottomToTop => Rect {\n            left: 0.0,\n            right: 1.0,\n            top: 1.0 - percentage,\n            bottom: 1.0,\n        },\n    };\n\n    make_widget!(slider_view)\n        .with_props(NavItemActive)\n        .with_props(props)\n        .named_slot(\n            \"content\",\n            make_widget!(content_box)\n                .listed_slot(\n                    make_widget!(image_box).with_props(ImageBoxProps::colored(Color {\n                        r: 0.0,\n                        g: 0.0,\n                        b: 1.0,\n                        a: 1.0,\n                    })),\n                )\n                .listed_slot(\n                    make_widget!(image_box)\n                        .with_props(ContentBoxItemLayout {\n                            anchors,\n                            ..Default::default()\n                        })\n                        .with_props(ImageBoxProps::colored(Color {\n                            r: 1.0,\n                            g: 0.0,\n                            b: 0.0,\n                            a: 1.0,\n                        })),\n                )\n                .listed_slot(make_widget!(text_box).with_props(TextBoxProps {\n                    text: value.to_string(),\n                    horizontal_align: TextBoxHorizontalAlign::Center,\n                    vertical_align: TextBoxVerticalAlign::Middle,\n                    font: TextBoxFont {\n                        name: \"./demos/hello-world/resources/verdana.ttf\".to_owned(),\n                        size: 64.0,\n                    },\n                    color: Color {\n                        r: 1.0,\n                        g: 1.0,\n                        b: 1.0,\n                        a: 1.0,\n                    },\n                    ..Default::default()\n                })),\n        )\n        .into()\n}\n\nfn main() {\n    let app = DeclarativeApp::default()\n        .tree(make_widget!(app))\n        .view_model(\n            DATA,\n            ViewModel::produce(|properties| AppData {\n                float_input: Managed::new(ViewModelValue::new(\n                    Default::default(),\n                    properties.notifier(FLOAT_INPUT),\n                )),\n                integer_input: Managed::new(ViewModelValue::new(\n                    Default::default(),\n                    properties.notifier(INTEGER_INPUT),\n                )),\n                unsigned_integer_input: Managed::new(ViewModelValue::new(\n                    Default::default(),\n                    properties.notifier(UNSIGNED_INTEGER_INPUT),\n                )),\n            }),\n        );\n\n    App::new(AppConfig::default().title(\"Slider View\")).run(app);\n}\n"
  },
  {
    "path": "crates/_/examples/space_box.rs",
    "content": "use raui_app::app::declarative::DeclarativeApp;\nuse raui_core::{\n    make_widget,\n    widget::{\n        component::{\n            containers::horizontal_box::horizontal_box,\n            image_box::{ImageBoxProps, image_box},\n            space_box::{SpaceBoxProps, space_box},\n        },\n        unit::flex::FlexBoxItemLayout,\n        utils::Color,\n    },\n};\n\nfn main() {\n    let tree = make_widget!(horizontal_box)\n        .listed_slot(\n            make_widget!(image_box).with_props(ImageBoxProps::colored(Color {\n                r: 1.0,\n                g: 0.25,\n                b: 0.25,\n                a: 1.0,\n            })),\n        )\n        .listed_slot(\n            make_widget!(space_box)\n                // cube spacing means we set same separation both horizontally and vertically.\n                .with_props(SpaceBoxProps::cube(64.0))\n                // we set clear flex box layout to disallow space box fluidity.\n                .with_props(FlexBoxItemLayout::cleared()),\n        )\n        .listed_slot(\n            make_widget!(image_box).with_props(ImageBoxProps::colored(Color {\n                r: 0.25,\n                g: 0.25,\n                b: 1.0,\n                a: 1.0,\n            })),\n        );\n\n    DeclarativeApp::simple(\"Space Box\", tree);\n}\n"
  },
  {
    "path": "crates/_/examples/switch_box.rs",
    "content": "use raui_app::{\n    app::{App, AppConfig, declarative::DeclarativeApp},\n    event::{ElementState, Event, VirtualKeyCode, WindowEvent},\n};\nuse raui_core::{\n    make_widget, pre_hooks,\n    view_model::ViewModel,\n    widget::{\n        component::{\n            containers::switch_box::{SwitchBoxProps, switch_box},\n            image_box::{ImageBoxProps, image_box},\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        utils::Color,\n    },\n};\n\nconst DATA: &str = \"data\";\n\nfn use_app(ctx: &mut WidgetContext) {\n    ctx.life_cycle.mount(|mut ctx| {\n        ctx.view_models\n            .bindings(DATA, \"\")\n            .unwrap()\n            .bind(ctx.id.to_owned());\n    });\n}\n\n#[pre_hooks(use_app)]\nfn app(mut ctx: WidgetContext) -> WidgetNode {\n    // we read value from view model created with app builder.\n    let active_index = ctx\n        .view_models\n        .view_model(DATA)\n        .unwrap()\n        .read::<usize>()\n        .map(|value| *value % 3)\n        .unwrap_or_default();\n\n    make_widget!(switch_box)\n        .with_props(SwitchBoxProps {\n            active_index: Some(active_index),\n            ..Default::default()\n        })\n        .listed_slot(\n            make_widget!(image_box).with_props(ImageBoxProps::colored(Color {\n                r: 1.0,\n                g: 0.25,\n                b: 0.25,\n                a: 1.0,\n            })),\n        )\n        .listed_slot(\n            make_widget!(image_box).with_props(ImageBoxProps::colored(Color {\n                r: 0.25,\n                g: 1.0,\n                b: 0.25,\n                a: 1.0,\n            })),\n        )\n        .listed_slot(\n            make_widget!(image_box).with_props(ImageBoxProps::colored(Color {\n                r: 0.25,\n                g: 0.25,\n                b: 1.0,\n                a: 1.0,\n            })),\n        )\n        .into()\n}\n\nfn main() {\n    let app = DeclarativeApp::default()\n        .tree(make_widget!(app))\n        .view_model(DATA, ViewModel::new_object(0usize))\n        .event(move |application, event, _, _| {\n            let mut data = application\n                .view_models\n                .get_mut(DATA)\n                .unwrap()\n                .write_notified::<usize>()\n                .unwrap();\n\n            if let Event::WindowEvent {\n                event: WindowEvent::KeyboardInput { input, .. },\n                ..\n            } = event\n                && input.state == ElementState::Pressed\n                && let Some(key) = input.virtual_keycode\n            {\n                match key {\n                    VirtualKeyCode::Key1 | VirtualKeyCode::Numpad1 => {\n                        // we modify app data with value that represent active switch index.\n                        *data = 0;\n                    }\n                    VirtualKeyCode::Key2 | VirtualKeyCode::Numpad2 => {\n                        *data = 1;\n                    }\n                    VirtualKeyCode::Key3 | VirtualKeyCode::Numpad3 => {\n                        *data = 2;\n                    }\n                    _ => {}\n                }\n            }\n            true\n        });\n\n    App::new(AppConfig::default().title(\"Switch Box\")).run(app);\n}\n"
  },
  {
    "path": "crates/_/examples/tabs_box.rs",
    "content": "use raui_app::app::declarative::DeclarativeApp;\nuse raui_core::{\n    make_widget, pre_hooks,\n    widget::{\n        component::{\n            containers::tabs_box::{\n                TabPlateProps, TabsBoxProps, TabsBoxTabsLocation, nav_tabs_box,\n            },\n            image_box::{ImageBoxProps, image_box},\n            interactive::navigation::{NavItemActive, use_nav_container_active},\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        utils::Color,\n    },\n};\n\n#[pre_hooks(use_nav_container_active)]\nfn app(mut ctx: WidgetContext) -> WidgetNode {\n    make_widget!(nav_tabs_box)\n        .with_props(NavItemActive)\n        .with_props(TabsBoxProps {\n            // top tabs location is default one but we can change tabs bar to be on either side of\n            // the tabs box area.\n            tabs_location: TabsBoxTabsLocation::Top,\n            // we set tabs basis to let tabs itself fill into the area that tabs bar gives to layout.\n            tabs_basis: Some(50.0),\n            ..Default::default()\n        })\n        // we pack pairs of tab plate and its content using tuples and then put them in listed slots.\n        .listed_slot(WidgetNode::pack_tuple([\n            // first tiple item is always the tab plate that's gonna be put on tabs bar (it's gonna\n            // be wrapped with button component so it's better to not put other buttons in tab plate\n            // widget tree).\n            make_widget!(tab_plate)\n                .with_props(Color {\n                    r: 1.0,\n                    g: 0.25,\n                    b: 0.25,\n                    a: 1.0,\n                })\n                .into(),\n            // second tuple item is always the tab contents (all tabs contents are put into inner\n            // switch box so we make sure there is always only one tab content present at a time).\n            make_widget!(image_box)\n                .with_props(ImageBoxProps::colored(Color {\n                    r: 0.75,\n                    g: 0.25,\n                    b: 0.25,\n                    a: 1.0,\n                }))\n                .into(),\n        ]))\n        .listed_slot(WidgetNode::pack_tuple([\n            make_widget!(tab_plate)\n                .with_props(Color {\n                    r: 0.25,\n                    g: 1.0,\n                    b: 0.25,\n                    a: 1.0,\n                })\n                .into(),\n            make_widget!(image_box)\n                .with_props(ImageBoxProps::colored(Color {\n                    r: 0.25,\n                    g: 0.75,\n                    b: 0.25,\n                    a: 1.0,\n                }))\n                .into(),\n        ]))\n        .listed_slot(WidgetNode::pack_tuple([\n            make_widget!(tab_plate)\n                .with_props(Color {\n                    r: 0.25,\n                    g: 0.25,\n                    b: 1.0,\n                    a: 1.0,\n                })\n                .into(),\n            make_widget!(image_box)\n                .with_props(ImageBoxProps::colored(Color {\n                    r: 0.25,\n                    g: 0.25,\n                    b: 0.75,\n                    a: 1.0,\n                }))\n                .into(),\n        ]))\n        .into()\n}\n\nfn tab_plate(ctx: WidgetContext) -> WidgetNode {\n    let mut color = ctx.props.read_cloned_or_default::<Color>();\n    if !ctx.props.read_cloned_or_default::<TabPlateProps>().active {\n        color.r *= 0.5;\n        color.g *= 0.5;\n        color.b *= 0.5;\n    }\n\n    make_widget!(image_box)\n        .with_props(ImageBoxProps::colored(color))\n        .into()\n}\n\nfn main() {\n    DeclarativeApp::simple(\"Tabs Box\", make_widget!(app));\n}\n"
  },
  {
    "path": "crates/_/examples/text_box.rs",
    "content": "use raui_app::app::declarative::DeclarativeApp;\nuse raui_core::{\n    make_widget,\n    widget::{\n        component::text_box::{TextBoxProps, text_box},\n        unit::text::{TextBoxFont, TextBoxHorizontalAlign, TextBoxVerticalAlign},\n        utils::Color,\n    },\n};\n\nfn main() {\n    let tree = make_widget!(text_box).with_props(TextBoxProps {\n        text: \"RAUI\\nText Box\".to_owned(),\n        font: TextBoxFont {\n            name: \"./demos/hello-world/resources/verdana.ttf\".to_owned(),\n            size: 64.0,\n        },\n        color: Color {\n            r: 0.0,\n            g: 0.0,\n            b: 0.5,\n            a: 1.0,\n        },\n        horizontal_align: TextBoxHorizontalAlign::Center,\n        vertical_align: TextBoxVerticalAlign::Middle,\n        ..Default::default()\n    });\n\n    DeclarativeApp::simple(\"Text Box\", tree);\n}\n"
  },
  {
    "path": "crates/_/examples/text_box_content_size.rs",
    "content": "use raui_app::app::declarative::DeclarativeApp;\nuse raui_core::{\n    make_widget,\n    widget::{\n        component::{\n            containers::horizontal_box::horizontal_box,\n            image_box::{ImageBoxProps, image_box},\n            text_box::{TextBoxProps, text_box},\n        },\n        unit::{\n            flex::FlexBoxItemLayout,\n            text::{TextBoxFont, TextBoxHorizontalAlign, TextBoxSizeValue},\n        },\n        utils::Color,\n    },\n};\n\nfn main() {\n    let tree = make_widget!(horizontal_box)\n        .listed_slot(\n            make_widget!(text_box)\n                .with_props(FlexBoxItemLayout {\n                    // Disable growing and shrinking of the text box to allow it to\n                    // take the size of its content in the list.\n                    grow: 0.0,\n                    shrink: 0.0,\n                    margin: 20.0.into(),\n                    ..Default::default()\n                })\n                .with_props(TextBoxProps {\n                    text: \"RAUI\\nContent Size\".to_owned(),\n                    font: TextBoxFont {\n                        name: \"./demos/hello-world/resources/verdana.ttf\".to_owned(),\n                        size: 64.0,\n                    },\n                    color: Color {\n                        r: 0.0,\n                        g: 0.0,\n                        b: 0.5,\n                        a: 1.0,\n                    },\n                    horizontal_align: TextBoxHorizontalAlign::Right,\n                    // Setting text size to its content allows for fitting other\n                    // widgets nicely around that text box.\n                    width: TextBoxSizeValue::Content,\n                    height: TextBoxSizeValue::Content,\n                    ..Default::default()\n                }),\n        )\n        .listed_slot(\n            make_widget!(image_box).with_props(ImageBoxProps::colored(Color {\n                r: 0.5,\n                g: 0.0,\n                b: 0.0,\n                a: 1.0,\n            })),\n        );\n\n    DeclarativeApp::simple(\"Text Box - Content Size\", tree);\n}\n"
  },
  {
    "path": "crates/_/examples/text_field_paper.rs",
    "content": "use raui_app::app::declarative::DeclarativeApp;\nuse raui_core::{\n    ManagedGc, make_widget, pre_hooks,\n    view_model::ViewModel,\n    widget::{\n        component::{\n            containers::size_box::{SizeBoxProps, size_box},\n            interactive::{\n                button::ButtonNotifyProps,\n                input_field::{TextInput, TextInputProps},\n                navigation::{NavItemActive, use_nav_container_active},\n            },\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::{size::SizeBoxSizeValue, text::TextBoxFont},\n        utils::Rect,\n    },\n};\nuse raui_material::{\n    component::{\n        containers::paper::paper,\n        interactive::text_field_paper::{TextFieldPaperProps, text_field_paper},\n    },\n    theme::{ThemeColor, ThemeProps, ThemedTextMaterial, ThemedWidgetProps, new_dark_theme},\n};\n\nconst TEXT_INPUT: &str = \"text-input\";\n\n// Create a new theme with a custom text variant for input fields.\nfn new_theme() -> ThemeProps {\n    let mut theme = new_dark_theme();\n    theme.text_variants.insert(\n        \"input\".to_owned(),\n        ThemedTextMaterial {\n            font: TextBoxFont {\n                name: \"./demos/hello-world/resources/verdana.ttf\".to_owned(),\n                size: 24.0,\n            },\n            ..Default::default()\n        },\n    );\n    theme\n}\n\nfn use_app(ctx: &mut WidgetContext) {\n    ctx.life_cycle.mount(|mut ctx| {\n        // Initialize the view model for the text input field.\n        let mut view_model = ViewModel::produce(|_| ManagedGc::new(\"Hello!\".to_owned()));\n        view_model\n            .properties\n            .bindings(TEXT_INPUT)\n            .unwrap()\n            .bind(ctx.id.to_owned());\n        ctx.view_models.widget_register(TEXT_INPUT, view_model);\n    });\n}\n\n#[pre_hooks(use_nav_container_active, use_app)]\nfn app(mut ctx: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        id,\n        mut view_models,\n        ..\n    } = ctx;\n\n    // Turn the view model into a lazy TextInput for the text field props.\n    let text = view_models.widget_view_model_mut(TEXT_INPUT).and_then(|v| {\n        v.read::<ManagedGc<String>>()\n            .map(|v| TextInput::new(v.lazy()))\n    });\n\n    make_widget!(paper)\n        .with_shared_props(new_theme())\n        .listed_slot(\n            make_widget!(size_box)\n                .with_props(SizeBoxProps {\n                    width: SizeBoxSizeValue::Fill,\n                    height: SizeBoxSizeValue::Exact(50.0),\n                    margin: 20.0.into(),\n                    ..Default::default()\n                })\n                .named_slot(\n                    \"content\",\n                    make_widget!(text_field_paper)\n                        .key(\"name\")\n                        .with_props(TextFieldPaperProps {\n                            hint: \"> Type some text...\".to_owned(),\n                            paper_theme: ThemedWidgetProps {\n                                color: ThemeColor::Primary,\n                                ..Default::default()\n                            },\n                            padding: Rect {\n                                left: 10.0,\n                                right: 10.0,\n                                top: 6.0,\n                                bottom: 6.0,\n                            },\n                            variant: \"input\".to_owned(),\n                            ..Default::default()\n                        })\n                        // Make input text editable.\n                        .with_props(NavItemActive)\n                        // Notify this widget about changes made by input text.\n                        .with_props(ButtonNotifyProps(id.to_owned().into()))\n                        // Pass the lazy TextInput to the text field paper to edit.\n                        .with_props(TextInputProps {\n                            text,\n                            ..Default::default()\n                        }),\n                ),\n        )\n        .into()\n}\n\nfn main() {\n    DeclarativeApp::simple(\"Text Field Paper\", make_widget!(app));\n}\n"
  },
  {
    "path": "crates/_/examples/tooltip_box.rs",
    "content": "// Make sure you have seen `context_box` code example first, because this is an evolution of that.\n\nuse raui_app::app::declarative::DeclarativeApp;\nuse raui_core::{\n    make_widget, pre_hooks,\n    widget::{\n        WidgetRef,\n        component::{\n            containers::{\n                anchor_box::PivotBoxProps,\n                content_box::content_box,\n                horizontal_box::{HorizontalBoxProps, horizontal_box},\n                portal_box::PortalsContainer,\n                tooltip_box::portals_tooltip_box,\n            },\n            image_box::{ImageBoxProps, image_box},\n            interactive::{\n                button::button,\n                navigation::{NavItemActive, use_nav_container_active},\n            },\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::{\n            flex::FlexBoxItemLayout,\n            image::{ImageBoxColor, ImageBoxMaterial, ImageBoxSizeValue},\n        },\n        utils::{Color, Vec2},\n    },\n};\n\n// we mark app as an active navigable container to let all buttons down the tree register to the\n// navigation system so they can react on mouse hovering for example.\n#[pre_hooks(use_nav_container_active)]\nfn app(mut ctx: WidgetContext) -> WidgetNode {\n    let idref = WidgetRef::default();\n\n    make_widget!(content_box)\n        .idref(idref.clone())\n        .with_shared_props(PortalsContainer(idref))\n        .listed_slot(\n            make_widget!(horizontal_box)\n                .with_props(HorizontalBoxProps {\n                    separation: 25.0,\n                    override_slots_layout: Some(FlexBoxItemLayout::cleared()),\n                    ..Default::default()\n                })\n                .listed_slot(make_widget!(icon).with_props(Color {\n                    r: 1.0,\n                    g: 0.25,\n                    b: 0.25,\n                    a: 1.0,\n                }))\n                .listed_slot(\n                    make_widget!(icon)\n                        .with_props(Color {\n                            r: 0.25,\n                            g: 1.0,\n                            b: 0.25,\n                            a: 1.0,\n                        })\n                        .with_props(PivotBoxProps {\n                            pivot: Vec2 { x: 0.5, y: 1.0 },\n                            align: Vec2 { x: 0.5, y: 0.0 },\n                        }),\n                )\n                .listed_slot(\n                    make_widget!(icon)\n                        .with_props(Color {\n                            r: 0.25,\n                            g: 0.25,\n                            b: 1.0,\n                            a: 1.0,\n                        })\n                        .with_props(PivotBoxProps {\n                            pivot: Vec2 { x: 1.0, y: 1.0 },\n                            align: Vec2 { x: 1.0, y: 0.0 },\n                        }),\n                ),\n        )\n        .into()\n}\n\nfn icon(ctx: WidgetContext) -> WidgetNode {\n    // tooltip box is basically an evolution of context box - what changes is tooltip box is shown\n    // only if this its content gets selected by navigation system (and since buttons can be\n    // selected for example by mouse hover, this tooltip is shown whenever mouse gets over the\n    // widget it wraps).\n    make_widget!(portals_tooltip_box)\n        .with_props(ctx.props.read_cloned_or_default::<PivotBoxProps>())\n        // put colored image box as content widget.\n        .named_slot(\n            \"content\",\n            // we wrap content with button to allow automated widget selection that will show tooltip,\n            make_widget!(button)\n                // remember that buttons has to be activated to make them receive selection\n                // navigation messages - they are inactive by default.\n                .with_props(NavItemActive)\n                .named_slot(\n                    \"content\",\n                    make_widget!(image_box).with_props(ImageBoxProps {\n                        material: ImageBoxMaterial::Color(ImageBoxColor {\n                            color: ctx.props.read_cloned_or_default::<Color>(),\n                            ..Default::default()\n                        }),\n                        width: ImageBoxSizeValue::Exact(100.0),\n                        height: ImageBoxSizeValue::Exact(100.0),\n                        ..Default::default()\n                    }),\n                ),\n        )\n        // put gray image box as tooltip widget.\n        .named_slot(\n            \"tooltip\",\n            make_widget!(image_box).with_props(ImageBoxProps {\n                material: ImageBoxMaterial::Color(ImageBoxColor {\n                    color: Color {\n                        r: 0.25,\n                        g: 0.25,\n                        b: 0.25,\n                        a: 1.0,\n                    },\n                    ..Default::default()\n                }),\n                width: ImageBoxSizeValue::Exact(150.0),\n                height: ImageBoxSizeValue::Exact(50.0),\n                ..Default::default()\n            }),\n        )\n        .into()\n}\n\nfn main() {\n    DeclarativeApp::simple(\"Tooltip Box\", make_widget!(app));\n}\n"
  },
  {
    "path": "crates/_/examples/tracking.rs",
    "content": "use raui_app::app::declarative::DeclarativeApp;\nuse raui_core::{\n    make_widget, pre_hooks,\n    widget::{\n        component::{\n            containers::horizontal_box::horizontal_box,\n            image_box::{ImageBoxProps, image_box},\n            interactive::navigation::{\n                NavTrackingNotifyMessage, NavTrackingNotifyProps, self_tracking,\n                use_nav_container_active,\n            },\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::flex::FlexBoxItemLayout,\n        utils::Color,\n    },\n};\n\nfn use_app(ctx: &mut WidgetContext) {\n    // whenever we receive tracking message, we store it's horizontal\n    // component in state for rendering to use.\n    ctx.life_cycle.change(|ctx| {\n        for msg in ctx.messenger.messages {\n            if let Some(msg) = msg.as_any().downcast_ref::<NavTrackingNotifyMessage>() {\n                let _ = ctx.state.write_with(msg.state.factor.x);\n            }\n        }\n    });\n}\n\n#[pre_hooks(use_nav_container_active, use_app)]\nfn app(mut ctx: WidgetContext) -> WidgetNode {\n    // possibly read stored horizontal tracking value.\n    let factor = ctx.state.read_cloned::<f32>().unwrap_or(0.5);\n\n    // we use `self_tracking` wrapper widget to allow it to automatically\n    // track pointer position relative to itself.\n    make_widget!(self_tracking)\n        // we tell widget to notify app widget about tracking changes.\n        .with_props(NavTrackingNotifyProps(ctx.id.to_owned().into()))\n        .named_slot(\n            \"content\",\n            // we make horizontal box items have weights proportional to\n            // horizontal tracking value.\n            make_widget!(horizontal_box)\n                .listed_slot(\n                    make_widget!(image_box)\n                        .with_props(FlexBoxItemLayout {\n                            grow: factor,\n                            shrink: factor,\n                            ..Default::default()\n                        })\n                        .with_props(ImageBoxProps::colored(Color {\n                            r: 1.0,\n                            g: 0.0,\n                            b: 0.0,\n                            a: 1.0,\n                        })),\n                )\n                .listed_slot(\n                    make_widget!(image_box)\n                        .with_props(FlexBoxItemLayout {\n                            grow: 1.0 - factor,\n                            shrink: 1.0 - factor,\n                            ..Default::default()\n                        })\n                        .with_props(ImageBoxProps::colored(Color {\n                            r: 0.0,\n                            g: 0.0,\n                            b: 1.0,\n                            a: 1.0,\n                        })),\n                ),\n        )\n        .into()\n}\n\nfn main() {\n    DeclarativeApp::simple(\"Tracking\", make_widget!(app));\n}\n"
  },
  {
    "path": "crates/_/examples/variant_box.rs",
    "content": "use raui_app::{\n    app::{App, AppConfig, declarative::DeclarativeApp},\n    event::{ElementState, Event, VirtualKeyCode, WindowEvent},\n};\nuse raui_core::{\n    make_widget, pre_hooks,\n    view_model::ViewModel,\n    widget::{\n        component::{\n            containers::variant_box::{VariantBoxProps, variant_box},\n            image_box::{ImageBoxProps, image_box},\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        utils::Color,\n    },\n};\n\nconst DATA: &str = \"data\";\n\nfn use_app(ctx: &mut WidgetContext) {\n    ctx.life_cycle.mount(|mut ctx| {\n        ctx.view_models\n            .bindings(DATA, \"\")\n            .unwrap()\n            .bind(ctx.id.to_owned());\n    });\n}\n\n#[pre_hooks(use_app)]\nfn app(mut ctx: WidgetContext) -> WidgetNode {\n    // we read value from view model created with app builder.\n    let variant_name = ctx\n        .view_models\n        .view_model(DATA)\n        .unwrap()\n        .read::<String>()\n        .map(|value| value.to_owned());\n\n    make_widget!(variant_box)\n        .with_props(VariantBoxProps { variant_name })\n        .named_slot(\n            \"A\",\n            make_widget!(image_box).with_props(ImageBoxProps::colored(Color {\n                r: 1.0,\n                g: 0.25,\n                b: 0.25,\n                a: 1.0,\n            })),\n        )\n        .named_slot(\n            \"S\",\n            make_widget!(image_box).with_props(ImageBoxProps::colored(Color {\n                r: 0.25,\n                g: 1.0,\n                b: 0.25,\n                a: 1.0,\n            })),\n        )\n        .named_slot(\n            \"D\",\n            make_widget!(image_box).with_props(ImageBoxProps::colored(Color {\n                r: 0.25,\n                g: 0.25,\n                b: 1.0,\n                a: 1.0,\n            })),\n        )\n        .into()\n}\n\nfn main() {\n    let app = DeclarativeApp::default()\n        .tree(make_widget!(app))\n        .view_model(DATA, ViewModel::new_object(\"A\".to_owned()))\n        .event(move |application, event, _, _| {\n            let mut data = application\n                .view_models\n                .get_mut(DATA)\n                .unwrap()\n                .write_notified::<String>()\n                .unwrap();\n\n            if let Event::WindowEvent {\n                event: WindowEvent::KeyboardInput { input, .. },\n                ..\n            } = event\n                && input.state == ElementState::Pressed\n                && let Some(key) = input.virtual_keycode\n            {\n                match key {\n                    VirtualKeyCode::A => {\n                        // we modify app data with value that represent active variant name.\n                        *data = \"A\".to_owned();\n                    }\n                    VirtualKeyCode::S => {\n                        *data = \"S\".to_owned();\n                    }\n                    VirtualKeyCode::D => {\n                        *data = \"D\".to_owned();\n                    }\n                    _ => {}\n                }\n            };\n            true\n        });\n\n    App::new(AppConfig::default().title(\"Variant Box\")).run(app);\n}\n"
  },
  {
    "path": "crates/_/examples/vertical_box.rs",
    "content": "use raui_app::app::declarative::DeclarativeApp;\nuse raui_core::{\n    make_widget,\n    widget::{\n        component::{\n            containers::vertical_box::{VerticalBoxProps, vertical_box},\n            image_box::{ImageBoxProps, image_box},\n        },\n        unit::flex::FlexBoxItemLayout,\n        utils::Color,\n    },\n};\n\nfn main() {\n    let tree = make_widget!(vertical_box)\n        .with_props(VerticalBoxProps {\n            separation: 50.0,\n            ..Default::default()\n        })\n        .listed_slot(\n            make_widget!(image_box)\n                .with_props(ImageBoxProps::colored(Color {\n                    r: 1.0,\n                    g: 0.25,\n                    b: 0.25,\n                    a: 1.0,\n                }))\n                .with_props(FlexBoxItemLayout {\n                    // basis sets exact height of the item.\n                    basis: Some(100.0),\n                    // weight of the item when its layout box has to grow in height.\n                    grow: 0.5,\n                    // weight of the item when its layout box has to shrink in height (0.0 means no shrinking).\n                    shrink: 0.0,\n                    ..Default::default()\n                }),\n        )\n        .listed_slot(\n            make_widget!(image_box).with_props(ImageBoxProps::colored(Color {\n                r: 0.25,\n                g: 1.0,\n                b: 0.25,\n                a: 1.0,\n            })),\n        )\n        .listed_slot(\n            make_widget!(image_box)\n                .with_props(ImageBoxProps::colored(Color {\n                    r: 0.25,\n                    g: 0.25,\n                    b: 1.0,\n                    a: 1.0,\n                }))\n                .with_props(FlexBoxItemLayout {\n                    basis: Some(100.0),\n                    grow: 0.0,\n                    shrink: 0.5,\n                    ..Default::default()\n                }),\n        );\n\n    DeclarativeApp::simple(\"Vertical Box\", tree);\n}\n"
  },
  {
    "path": "crates/_/examples/view_model.rs",
    "content": "// Make sure you have seen `text_box` code example first, because this is an evolution of that.\n\nuse raui_app::{\n    app::{App, AppConfig, declarative::DeclarativeApp},\n    event::{ElementState, Event, VirtualKeyCode, WindowEvent},\n};\nuse raui_core::{\n    make_widget, pre_hooks,\n    view_model::{ViewModel, ViewModelValue},\n    widget::{\n        component::text_box::{TextBoxProps, text_box},\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::text::TextBoxFont,\n        utils::Color,\n    },\n};\n\n// Name of View-Model instance.\nconst DATA: &str = \"data\";\n// Name of View-Model property notification.\nconst COUNTER: &str = \"counter\";\n\n// View-Model data type.\nstruct AppData {\n    // View-Model value wrapper that automatically notifies View-Model\n    // about change on mutation.\n    counter: ViewModelValue<usize>,\n}\n\n// We use hook to bind widget to and unbind from View-Model instance.\n// This will make RAUI application automatically rebuild widgets tree\n// on change in View-Model data.\n// BTW. We could omit unbinding, since widgets unbind automatically\n// on unmount, but this is here to showcase how to do it manually.\nfn use_app(ctx: &mut WidgetContext) {\n    ctx.life_cycle.mount(|mut ctx| {\n        ctx.view_models\n            .bindings(DATA, COUNTER)\n            .unwrap()\n            .bind(ctx.id.to_owned());\n    });\n\n    ctx.life_cycle.unmount(|mut ctx| {\n        ctx.view_models\n            .bindings(DATA, COUNTER)\n            .unwrap()\n            .unbind(ctx.id);\n    });\n}\n\n#[pre_hooks(use_app)]\nfn app(mut ctx: WidgetContext) -> WidgetNode {\n    // We read app data from view model created with app builder.\n    let app_data = ctx\n        .view_models\n        .view_model(DATA)\n        .unwrap()\n        .read::<AppData>()\n        .unwrap();\n\n    make_widget!(text_box)\n        .with_props(TextBoxProps {\n            // Use View-Model data to render widget on change.\n            text: format!(\"Counter: {}\", *app_data.counter),\n            font: TextBoxFont {\n                name: \"./demos/hello-world/resources/verdana.ttf\".to_owned(),\n                size: 48.0,\n            },\n            color: Color {\n                r: 0.0,\n                g: 0.0,\n                b: 0.0,\n                a: 1.0,\n            },\n            ..Default::default()\n        })\n        .into()\n}\n\nfn main() {\n    // Create View-Model for `AppData`.\n    let view_model = ViewModel::produce(|properties| AppData {\n        // We use View-Model properties to create notifiers for properties.\n        counter: ViewModelValue::new(0, properties.notifier(COUNTER)),\n    });\n    // Get lazy shared reference to View-Model data for later use\n    // on the host side of application.\n    let app_data = view_model.lazy::<AppData>().unwrap();\n\n    let app = DeclarativeApp::default()\n        .tree(make_widget!(app))\n        .view_model(DATA, view_model)\n        .event(move |_, event, _, _| {\n            if let Event::WindowEvent {\n                event: WindowEvent::KeyboardInput { input, .. },\n                ..\n            } = event\n                && let Some(key) = input.virtual_keycode\n                && input.state == ElementState::Pressed\n                && key == VirtualKeyCode::Space\n            {\n                // Here we use that shared reference to `AppData`\n                // to mutate it and notify UI.\n                *app_data.write().unwrap().counter += 1;\n            };\n            true\n        });\n\n    App::new(AppConfig::default().title(\"View-Model\")).run(app);\n}\n"
  },
  {
    "path": "crates/_/examples/view_model_hierarchy.rs",
    "content": "// Make sure you have seen `view_model_widget` code example first, because this is an evolution of that.\n\nuse raui_app::app::declarative::DeclarativeApp;\nuse raui_core::{\n    make_widget, pre_hooks,\n    view_model::{ViewModel, ViewModelValue},\n    widget::{\n        component::{\n            containers::vertical_box::nav_vertical_box,\n            image_box::{ImageBoxProps, image_box},\n            interactive::{\n                button::{ButtonNotifyMessage, ButtonNotifyProps, button},\n                navigation::NavItemActive,\n            },\n            text_box::{TextBoxProps, text_box},\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::text::TextBoxFont,\n        utils::Color,\n    },\n};\n\nconst DATA: &str = \"data\";\nconst COUNTER: &str = \"counter\";\n\nstruct AppData {\n    counter: ViewModelValue<usize>,\n}\n\nfn use_app(ctx: &mut WidgetContext) {\n    ctx.life_cycle.mount(|mut ctx| {\n        // We register View-Model to `app` widget.\n        let mut view_model = ViewModel::produce(|properties| AppData {\n            counter: ViewModelValue::new(0, properties.notifier(COUNTER)),\n        });\n        view_model\n            .properties\n            .bindings(COUNTER)\n            .unwrap()\n            .bind(ctx.id.to_owned());\n        ctx.view_models.widget_register(DATA, view_model);\n    });\n}\n\n#[pre_hooks(use_app)]\nfn app(mut ctx: WidgetContext) -> WidgetNode {\n    // We read View-Model from `app` widget.\n    let counter = ctx\n        .view_models\n        .widget_view_model(DATA)\n        .and_then(|view_model| view_model.read::<AppData>().map(|data| *data.counter))\n        .unwrap_or_default();\n\n    make_widget!(nav_vertical_box)\n        .listed_slot(make_widget!(text_box).with_props(TextBoxProps {\n            text: format!(\"Counter: {counter}\"),\n            font: TextBoxFont {\n                name: \"./demos/hello-world/resources/verdana.ttf\".to_owned(),\n                size: 48.0,\n            },\n            color: Color {\n                r: 0.0,\n                g: 0.0,\n                b: 0.0,\n                a: 1.0,\n            },\n            ..Default::default()\n        }))\n        .listed_slot(make_widget!(trigger))\n        .into()\n}\n\nfn use_trigger(ctx: &mut WidgetContext) {\n    ctx.life_cycle.change(|mut ctx| {\n        // We write to View-Model in hierarchy of `app` widget branch,\n        // that happen to be parent of this `trigger` widget.\n        // Useful for data storages cascading down the hierarchy tree.\n        // Each level of the hierarchy can also \"override\" View-Models.\n        let mut app_data = ctx\n            .view_models\n            .hierarchy_view_model_mut(DATA)\n            .unwrap()\n            .write::<AppData>()\n            .unwrap();\n\n        for msg in ctx.messenger.messages {\n            if let Some(msg) = msg.as_any().downcast_ref::<ButtonNotifyMessage>() {\n                if msg.trigger_start() {\n                    *app_data.counter = app_data.counter.saturating_add(1);\n                } else if msg.context_start() {\n                    *app_data.counter = app_data.counter.saturating_sub(1);\n                }\n            }\n        }\n    });\n}\n\n#[pre_hooks(use_trigger)]\nfn trigger(mut ctx: WidgetContext) -> WidgetNode {\n    make_widget!(button)\n        .with_props(NavItemActive)\n        .with_props(ButtonNotifyProps(ctx.id.to_owned().into()))\n        .named_slot(\n            \"content\",\n            make_widget!(image_box).with_props(ImageBoxProps::colored(Color {\n                r: 0.5,\n                g: 1.0,\n                b: 1.0,\n                a: 1.0,\n            })),\n        )\n        .into()\n}\n\nfn main() {\n    DeclarativeApp::simple(\"View-Model - Hierarchy\", make_widget!(app));\n}\n"
  },
  {
    "path": "crates/_/examples/view_model_widget.rs",
    "content": "// Make sure you have seen `view_model` code example first, because this is an evolution of that.\n\nuse raui_app::app::declarative::DeclarativeApp;\nuse raui_core::{\n    animator::{AnimatedValue, Animation, AnimationMessage},\n    make_widget, pre_hooks,\n    view_model::{ViewModel, ViewModelValue},\n    widget::{\n        component::text_box::{TextBoxProps, text_box},\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::text::TextBoxFont,\n        utils::Color,\n    },\n};\n\n// animation message name used to trigger counter change.\nconst TICK: &str = \"tick\";\n// View-Model name.\nconst DATA: &str = \"data\";\n// View-Model proeprty name.\nconst COUNTER: &str = \"counter\";\n\nstruct AppData {\n    counter: ViewModelValue<usize>,\n}\n\nfn use_app(ctx: &mut WidgetContext) {\n    ctx.life_cycle.mount(|mut ctx| {\n        // First we register View-Model owned by this widget.\n        // Widget View-Models are replacements for widget state useful\n        // in situations where widget state should not be serializable props.\n        let mut view_model = ViewModel::produce(|properties| AppData {\n            counter: ViewModelValue::new(0, properties.notifier(COUNTER)),\n        });\n        view_model\n            .properties\n            .bindings(COUNTER)\n            .unwrap()\n            .bind(ctx.id.to_owned());\n        ctx.view_models.widget_register(DATA, view_model);\n\n        // Then we register new looped tick animation to trigger counter ticks.\n        let _ = ctx.animator.change(\n            TICK,\n            Some(Animation::Looped(Box::new(Animation::Sequence(vec![\n                Animation::Value(AnimatedValue {\n                    duration: 1.0,\n                    ..Default::default()\n                }),\n                Animation::Message(TICK.to_owned()),\n            ])))),\n        );\n    });\n\n    ctx.life_cycle.change(|mut ctx| {\n        // We get View-Model of this widget.\n        let mut app_data = ctx\n            .view_models\n            .widget_view_model_mut(DATA)\n            .unwrap()\n            .write::<AppData>()\n            .unwrap();\n\n        // And then we react for tick messages from animation.\n        for msg in ctx.messenger.messages {\n            if let Some(msg) = msg.as_any().downcast_ref::<AnimationMessage>()\n                && msg.0 == TICK\n            {\n                *app_data.counter += 1;\n            }\n        }\n    });\n}\n\n#[pre_hooks(use_app)]\nfn app(mut ctx: WidgetContext) -> WidgetNode {\n    // Because widget rendering can happen before widget mount,\n    // we need to fallback to some default value in case View-Model\n    // is not yet available.\n    let counter = ctx\n        .view_models\n        .widget_view_model(DATA)\n        .and_then(|view_model| view_model.read::<AppData>().map(|data| *data.counter))\n        .unwrap_or_default();\n\n    make_widget!(text_box)\n        .with_props(TextBoxProps {\n            text: format!(\"Counter: {counter}\"),\n            font: TextBoxFont {\n                name: \"./demos/hello-world/resources/verdana.ttf\".to_owned(),\n                size: 48.0,\n            },\n            color: Color {\n                r: 0.0,\n                g: 0.0,\n                b: 0.0,\n                a: 1.0,\n            },\n            ..Default::default()\n        })\n        .into()\n}\n\nfn main() {\n    DeclarativeApp::simple(\"View-Model - Widget\", make_widget!(app));\n}\n"
  },
  {
    "path": "crates/_/examples/wrap_box.rs",
    "content": "use raui_app::app::declarative::DeclarativeApp;\nuse raui_core::{\n    make_widget,\n    widget::{\n        component::{\n            containers::wrap_box::{WrapBoxProps, wrap_box},\n            image_box::{ImageBoxProps, image_box},\n        },\n        utils::Color,\n    },\n};\n\nfn main() {\n    let tree = make_widget!(wrap_box)\n        .with_props(WrapBoxProps {\n            // wrap box just wraps its content with margin.\n            margin: 50.0.into(),\n            ..Default::default()\n        })\n        .named_slot(\n            \"content\",\n            make_widget!(image_box).with_props(ImageBoxProps::colored(Color {\n                r: 1.0,\n                g: 0.25,\n                b: 0.25,\n                a: 1.0,\n            })),\n        );\n\n    DeclarativeApp::simple(\"Wrap Box\", tree);\n}\n"
  },
  {
    "path": "crates/_/src/import_all.rs",
    "content": "#![allow(ambiguous_glob_reexports)]\n#![allow(unused_variables)]\npub use raui_core::animator::*;\npub use raui_core::application::*;\npub use raui_core::interactive::*;\npub use raui_core::interactive::default_interactions_engine::*;\npub use raui_core::layout::*;\npub use raui_core::layout::default_layout_engine::*;\npub use raui_core::*;\npub use raui_core::messenger::*;\npub use raui_core::props::*;\npub use raui_core::renderer::*;\npub use raui_core::signals::*;\npub use raui_core::state::*;\npub use raui_core::tester::*;\npub use raui_core::view_model::*;\npub use raui_core::widget::*;\npub use raui_core::widget::component::*;\npub use raui_core::widget::component::containers::*;\npub use raui_core::widget::component::containers::anchor_box::*;\npub use raui_core::widget::component::containers::area_box::*;\npub use raui_core::widget::component::containers::content_box::*;\npub use raui_core::widget::component::containers::context_box::*;\npub use raui_core::widget::component::containers::flex_box::*;\npub use raui_core::widget::component::containers::float_box::*;\npub use raui_core::widget::component::containers::grid_box::*;\npub use raui_core::widget::component::containers::hidden_box::*;\npub use raui_core::widget::component::containers::horizontal_box::*;\npub use raui_core::widget::component::containers::portal_box::*;\npub use raui_core::widget::component::containers::responsive_box::*;\npub use raui_core::widget::component::containers::scroll_box::*;\npub use raui_core::widget::component::containers::size_box::*;\npub use raui_core::widget::component::containers::switch_box::*;\npub use raui_core::widget::component::containers::tabs_box::*;\npub use raui_core::widget::component::containers::tooltip_box::*;\npub use raui_core::widget::component::containers::variant_box::*;\npub use raui_core::widget::component::containers::vertical_box::*;\npub use raui_core::widget::component::containers::wrap_box::*;\npub use raui_core::widget::component::image_box::*;\npub use raui_core::widget::component::interactive::*;\npub use raui_core::widget::component::interactive::button::*;\npub use raui_core::widget::component::interactive::float_view::*;\npub use raui_core::widget::component::interactive::input_field::*;\npub use raui_core::widget::component::interactive::navigation::*;\npub use raui_core::widget::component::interactive::options_view::*;\npub use raui_core::widget::component::interactive::scroll_view::*;\npub use raui_core::widget::component::interactive::slider_view::*;\npub use raui_core::widget::component::space_box::*;\npub use raui_core::widget::component::text_box::*;\npub use raui_core::widget::context::*;\npub use raui_core::widget::node::*;\npub use raui_core::widget::unit::*;\npub use raui_core::widget::unit::area::*;\npub use raui_core::widget::unit::content::*;\npub use raui_core::widget::unit::flex::*;\npub use raui_core::widget::unit::grid::*;\npub use raui_core::widget::unit::image::*;\npub use raui_core::widget::unit::portal::*;\npub use raui_core::widget::unit::size::*;\npub use raui_core::widget::unit::text::*;\npub use raui_core::widget::utils::*;\n#[cfg(feature = \"material\")]\npub use raui_material::component::*;\n#[cfg(feature = \"material\")]\npub use raui_material::component::containers::*;\n#[cfg(feature = \"material\")]\npub use raui_material::component::containers::context_paper::*;\n#[cfg(feature = \"material\")]\npub use raui_material::component::containers::flex_paper::*;\n#[cfg(feature = \"material\")]\npub use raui_material::component::containers::grid_paper::*;\n#[cfg(feature = \"material\")]\npub use raui_material::component::containers::horizontal_paper::*;\n#[cfg(feature = \"material\")]\npub use raui_material::component::containers::modal_paper::*;\n#[cfg(feature = \"material\")]\npub use raui_material::component::containers::paper::*;\n#[cfg(feature = \"material\")]\npub use raui_material::component::containers::scroll_paper::*;\n#[cfg(feature = \"material\")]\npub use raui_material::component::containers::text_tooltip_paper::*;\n#[cfg(feature = \"material\")]\npub use raui_material::component::containers::tooltip_paper::*;\n#[cfg(feature = \"material\")]\npub use raui_material::component::containers::vertical_paper::*;\n#[cfg(feature = \"material\")]\npub use raui_material::component::containers::window_paper::*;\n#[cfg(feature = \"material\")]\npub use raui_material::component::containers::wrap_paper::*;\n#[cfg(feature = \"material\")]\npub use raui_material::component::icon_paper::*;\n#[cfg(feature = \"material\")]\npub use raui_material::component::interactive::*;\n#[cfg(feature = \"material\")]\npub use raui_material::component::interactive::button_paper::*;\n#[cfg(feature = \"material\")]\npub use raui_material::component::interactive::icon_button_paper::*;\n#[cfg(feature = \"material\")]\npub use raui_material::component::interactive::slider_paper::*;\n#[cfg(feature = \"material\")]\npub use raui_material::component::interactive::switch_button_paper::*;\n#[cfg(feature = \"material\")]\npub use raui_material::component::interactive::text_button_paper::*;\n#[cfg(feature = \"material\")]\npub use raui_material::component::interactive::text_field_paper::*;\n#[cfg(feature = \"material\")]\npub use raui_material::component::switch_paper::*;\n#[cfg(feature = \"material\")]\npub use raui_material::component::text_paper::*;\n#[cfg(feature = \"material\")]\npub use raui_material::*;\n#[cfg(feature = \"material\")]\npub use raui_material::theme::*;\n#[cfg(feature = \"retained\")]\npub use raui_retained::*;\n#[cfg(feature = \"immediate\")]\npub use raui_immediate::*;\n#[cfg(feature = \"immediate-widgets\")]\npub use raui_immediate_widgets::*;\n#[cfg(feature = \"tesselate\")]\npub use raui_tesselate_renderer::*;\n#[cfg(feature = \"json\")]\npub use raui_json_renderer::*;\n#[cfg(feature = \"app\")]\npub use raui_app::app::*;\n#[cfg(feature = \"app\")]\npub use raui_app::app::declarative::*;\n#[cfg(feature = \"app\")]\npub use raui_app::app::immediate::*;\n#[cfg(feature = \"app\")]\npub use raui_app::app::retained::*;\n#[cfg(feature = \"app\")]\npub use raui_app::components::*;\n#[cfg(feature = \"app\")]\npub use raui_app::components::canvas::*;\n#[cfg(feature = \"app\")]\npub use raui_app::*;\n#[cfg(feature = \"app\")]\npub use raui_app::render_worker::*;\n"
  },
  {
    "path": "crates/_/src/lib.rs",
    "content": "//! RAUI is a renderer agnostic UI system that is heavily inspired by **React**'s declarative UI\n//! composition and the **Unreal Engine Slate** widget components system.\n//!\n//! > 🗣 **Pronunciation:** RAUI is pronounced like **\"ra\"** ( the Egyptian god ) + **\"oui\"**\n//! > (french for \"yes\" ) — [Audio Example][pronounciation].\n//!\n//! [pronounciation]: https://itinerarium.github.io/phoneme-synthesis/?w=/%27rawi/\n//!\n//! The main idea behind RAUI architecture is to treat UI as another data source that you transform\n//! into your target renderable data format used by your rendering engine of choice.\n//!\n//! # Architecture\n//!\n//! ## [`Application`]\n//!\n//! [`Application`] is the central point of user interest. It performs whole UI processing logic.\n//! There you apply widget tree that wil be processed, send messages from host application to\n//! widgets and receive signals sent from widgets to host application.\n//!\n//!\n//! ## Widgets\n//!\n//! Widgets are divided into three categories:\n//! - **[`WidgetNode`]** - used as source UI trees (variant that can be either a component, unit or\n//!   none)\n//!\n//!\n//! - **[`WidgetComponent`]** - you can think of them as Virtual DOM nodes, they store:\n//!   - pointer to _component function_ (that process their data)\n//!   - unique _key_ (that is a part of widget ID and will be used to tell the system if it should\n//!     carry its _state_ to next processing run)\n//!   - boxed cloneable _properties_ data\n//!   - _listed slots_ (simply: widget children)\n//!   - _named slots_ (similar to listed slots: widget children, but these ones have names assigned\n//!     to them, so you can access them by name instead of by index)\n//! - **[`WidgetUnit`]** - an atomic element that renderers use to convert into target renderable\n//!   data format for rendering engine of choice.\n//!\n//! ## Component Function\n//!\n//! Component functions are static functions that transforms input data (properties, state or\n//! neither of them) into output widget tree (usually used to simply wrap another components tree\n//! under one simple component, where at some point the simplest components returns final\n//! _[`WidgetUnit`]'s_). They work together as a chain of transforms - root component applies some\n//! properties into children components using data from its own properties or state.\n//!\n//! ### States\n//!\n//! This may bring up a question: _**\"If i use only functions and no objects to tell how to\n//! visualize UI, how do i keep some data between each render run?\"**_. For that you use _states_.\n//! State is a data that is stored between each processing calls as long as given widget is alive\n//! (that means: as long as widget id stays the same between two processing calls, to make sure your\n//! widget stays the same, you use keys - if no key is assigned, system will generate one for your\n//! widget but that will make it possible to die at any time if for example number of widget\n//! children changes in your common parent, your widget will change its id when key wasn't\n//! assigned). Some additional notes: While you use _properties_ to send information down the tree\n//! and _states_ to store widget data between processing cals, you can communicate with another\n//! widgets and host application using messages and signals! More than that, you can use hooks to\n//! listen for widget life cycle and perform actions there. It's worth noting that state uses\n//! _properties_ to hold its data, so by that you can for example attach multiple hooks that each of\n//! them uses different data type as widget state, this opens the doors to be very creative when\n//! combining different hooks that operate on the same widget.\n//!\n//! ## Hooks\n//!\n//! Hooks are used to put common widget logic into separate functions that can be chained in widgets\n//! and another hooks (you can build a reusable dependency chain of logic with that). Usually it is\n//! used to listen for life cycle events such as mount, change and unmount, additionally you can\n//! chain hooks to be processed sequentially in order they are chained in widgets and other hooks.\n//!\n//! What happens under the hood:\n//! - Application calls `button` on a node\n//!     - `button` calls `use_button` hook\n//!         - `use_button` calls `use_empty` hook\n//!     - `use_button` logic is executed\n//! - `button` logic is executed\n//!\n//! ## Layouting\n//!\n//! RAUI exposes the [`Application::layout()`][core::application::Application::layout] API to allow\n//! use of virtual-to-real coords mapping and custom layout engines to perform widget tree\n//! positioning data, which is later used by custom UI renderers to specify boxes where given\n//! widgets should be placed. Every call to perform layouting will store a layout data inside\n//! Application, you can always access that data at any time. There is a [`DefaultLayoutEngine`]\n//! that does this in a generic way. If you find some part of its pipeline working different than\n//! what you've expected, feel free to create your custom layout engine!\n//!\n//! ## Interactivity\n//!\n//! RAUI allows you to ease and automate interactions with UI by use of Interactions Engine - this\n//! is just a struct that implements [`perform_interactions`] method with reference to Application,\n//! and all you should do there is to send user input related messages to widgets. There is\n//! [`DefaultInteractionsEngine`] that covers widget navigation, button and input field - actions\n//! sent from input devices such as mouse (or any single pointer), keyboard and gamepad. When it\n//! comes to UI navigation you can send raw [`NavSignal`] messages to the default interactions\n//! engine and despite being able to select/unselect widgets at will, you have typical navigation\n//! actions available: up, down, left, right, previous tab/screen, next tab/screen, also being able\n//! to focus text inputs and send text input changes to focused input widget. All interactive widget\n//! components that are provided by RAUI handle all [`NavSignal`] actions in their hooks, so all\n//! user has to do is to just activate navigation features for them (using [`NavItemActive`] unit\n//! props). RAUI integrations that want to just use use default interactions engine should make use\n//! of this struct composed in them and call its [`interact`] method with information about what\n//! input change was made. There is an example of that feature covered in RAUI App crate\n//! (`AppInteractionsEngine` struct).\n//!\n//! **NOTE: Interactions engines should use layout for pointer events so make sure that you rebuild\n//! layout before you perform interactions!**\n//!\n//! [`Application`]: core::application::Application\n//! [`WidgetNode`]: core::widget::node::WidgetNode\n//! [`WidgetComponent`]: core::widget::component::WidgetComponent\n//! [`WidgetUnit`]: core::widget::unit::WidgetUnit\n//! [`DefaultLayoutEngine`]: core::layout::default_layout_engine::DefaultLayoutEngine\n//! [`NavSignal`]: core::widget::component::interactive::navigation::NavSignal\n//! [`NavItemActive`]: core::widget::component::interactive::navigation::NavItemActive\n//! [`perform_interactions`]: core::interactive::InteractionsEngine::perform_interactions\n//! [`interact`]:\n//! core::interactive::default_interactions_engine::DefaultInteractionsEngine::interact\n//! [`DefaultInteractionsEngine`]:\n//! core::interactive::default_interactions_engine::DefaultInteractionsEngine\n\n#[doc(inline)]\npub use raui_core as core;\n\n#[doc(inline)]\n#[cfg(feature = \"material\")]\npub use raui_material as material;\n\n/// Renderer implementations\npub mod renderer {\n    #[cfg(feature = \"json\")]\n    pub mod json {\n        pub use raui_json_renderer::*;\n    }\n    #[cfg(feature = \"tesselate\")]\n    pub mod tesselate {\n        pub use raui_tesselate_renderer::*;\n    }\n}\n\n#[doc(inline)]\n#[cfg(feature = \"app\")]\npub use raui_app as app;\n\n#[doc(hidden)]\n#[cfg(feature = \"import-all\")]\npub mod import_all;\n"
  },
  {
    "path": "crates/app/Cargo.toml",
    "content": "[package]\nname = \"raui-app\"\nversion = \"0.70.17\"\nauthors = [\"Patryk 'PsichiX' Budzynski <psichix@gmail.com>\"]\nedition = \"2024\"\ndescription = \"RAUI application layer to focus only on making UI\"\nreadme = \"../../README.md\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/RAUI-labs/raui\"\nkeywords = [\"renderer\", \"agnostic\", \"ui\", \"interface\", \"gamedev\"]\ncategories = [\"gui\", \"rendering::graphics-api\"]\n\n[dependencies]\nraui-core = { path = \"../core\", version = \"0.70\" }\nraui-material = { path = \"../material\", version = \"0.70\" }\nraui-immediate = { path = \"../immediate\", version = \"0.70\" }\nraui-retained = { path = \"../retained\", version = \"0.70\" }\nraui-tesselate-renderer = { path = \"../tesselate-renderer\", version = \"0.70\" }\nspitfire-core = \"0.36\"\nspitfire-glow = \"0.36\"\nspitfire-fontdue = \"0.36\"\nbytemuck = { version = \"1\", features = [\"derive\"] }\nglutin = \"0.28\"\nfontdue = \"0.9\"\nimage = \"0.25\"\nvek = \"0.17\"\nserde = { version = \"1\", features = [\"derive\"] }\ntoml = \"0.9\"\n"
  },
  {
    "path": "crates/app/src/app/declarative.rs",
    "content": "use crate::{Vertex, app::SharedApp, interactions::AppInteractionsEngine};\nuse glutin::{event::Event, window::Window};\nuse raui_core::{\n    application::Application,\n    interactive::default_interactions_engine::DefaultInteractionsEngine,\n    layout::CoordsMappingScaling,\n    view_model::ViewModel,\n    widget::{node::WidgetNode, utils::Color},\n};\nuse spitfire_fontdue::TextRenderer;\nuse spitfire_glow::{\n    app::{App, AppConfig, AppControl, AppState},\n    graphics::Graphics,\n};\n\n#[derive(Default)]\npub struct DeclarativeApp {\n    shared: SharedApp,\n}\n\nimpl DeclarativeApp {\n    pub fn simple(title: impl ToString, root: impl Into<WidgetNode>) {\n        App::<Vertex>::new(AppConfig::default().title(title)).run(Self::default().tree(root));\n    }\n\n    pub fn simple_scaled(\n        title: impl ToString,\n        scaling: CoordsMappingScaling,\n        root: impl Into<WidgetNode>,\n    ) {\n        App::<Vertex>::new(AppConfig::default().title(title))\n            .run(Self::default().coords_mapping_scaling(scaling).tree(root));\n    }\n\n    pub fn simple_fullscreen(title: impl ToString, root: impl Into<WidgetNode>) {\n        App::<Vertex>::new(AppConfig::default().title(title).fullscreen(true))\n            .run(Self::default().tree(root));\n    }\n\n    pub fn simple_fullscreen_scaled(\n        title: impl ToString,\n        scaling: CoordsMappingScaling,\n        root: impl Into<WidgetNode>,\n    ) {\n        App::<Vertex>::new(AppConfig::default().title(title).fullscreen(true))\n            .run(Self::default().coords_mapping_scaling(scaling).tree(root));\n    }\n\n    pub fn update(mut self, f: impl FnMut(&mut Application, &mut AppControl) + 'static) -> Self {\n        self.shared.on_update = Some(Box::new(f));\n        self\n    }\n\n    pub fn redraw(\n        mut self,\n        f: impl FnMut(f32, &mut Graphics<Vertex>, &mut TextRenderer<Color>, &mut AppControl) + 'static,\n    ) -> Self {\n        self.shared.on_redraw = Some(Box::new(f));\n        self\n    }\n\n    pub fn event(\n        mut self,\n        f: impl FnMut(&mut Application, Event<()>, &mut Window, &mut DefaultInteractionsEngine) -> bool\n        + 'static,\n    ) -> Self {\n        self.shared.on_event = Some(Box::new(f));\n        self\n    }\n\n    pub fn setup(mut self, mut f: impl FnMut(&mut Application)) -> Self {\n        f(&mut self.shared.application);\n        self\n    }\n\n    pub fn setup_interactions(mut self, mut f: impl FnMut(&mut AppInteractionsEngine)) -> Self {\n        f(&mut self.shared.interactions);\n        self\n    }\n\n    pub fn view_model(mut self, name: impl ToString, view_model: ViewModel) -> Self {\n        self.shared\n            .application\n            .view_models\n            .insert(name.to_string(), view_model);\n        self\n    }\n\n    pub fn tree(mut self, root: impl Into<WidgetNode>) -> Self {\n        self.shared.application.apply(root);\n        self\n    }\n\n    pub fn coords_mapping_scaling(mut self, value: CoordsMappingScaling) -> Self {\n        self.shared.coords_mapping_scaling = value;\n        self\n    }\n}\n\nimpl AppState<Vertex> for DeclarativeApp {\n    fn on_init(&mut self, graphics: &mut Graphics<Vertex>, _: &mut AppControl) {\n        self.shared.init(graphics);\n    }\n\n    fn on_redraw(&mut self, graphics: &mut Graphics<Vertex>, control: &mut AppControl) {\n        self.shared.redraw(graphics, control);\n    }\n\n    fn on_event(&mut self, event: Event<()>, window: &mut Window) -> bool {\n        self.shared.event(event, window)\n    }\n}\n"
  },
  {
    "path": "crates/app/src/app/immediate.rs",
    "content": "use crate::{Vertex, app::SharedApp, interactions::AppInteractionsEngine};\nuse glutin::{event::Event, window::Window};\nuse raui_core::{\n    application::Application,\n    interactive::default_interactions_engine::DefaultInteractionsEngine,\n    layout::CoordsMappingScaling,\n    make_widget,\n    tester::{AppCycleFrameRunner, AppCycleTester},\n    widget::{component::containers::content_box::content_box, utils::Color},\n};\nuse raui_immediate::{ImmediateContext, make_widgets};\nuse spitfire_fontdue::TextRenderer;\nuse spitfire_glow::{\n    app::{App, AppConfig, AppControl, AppState},\n    graphics::Graphics,\n};\n\n#[derive(Default)]\npub struct ImmediateApp {\n    shared: SharedApp,\n}\n\nimpl ImmediateApp {\n    pub fn simple(title: impl ToString, callback: impl FnMut(&mut AppControl) + 'static) {\n        App::<Vertex>::new(AppConfig::default().title(title)).run(Self::default().update(callback));\n    }\n\n    pub fn simple_scaled(\n        title: impl ToString,\n        scaling: CoordsMappingScaling,\n        callback: impl FnMut(&mut AppControl) + 'static,\n    ) {\n        App::<Vertex>::new(AppConfig::default().title(title)).run(\n            Self::default()\n                .coords_mapping_scaling(scaling)\n                .update(callback),\n        );\n    }\n\n    pub fn simple_fullscreen(\n        title: impl ToString,\n        callback: impl FnMut(&mut AppControl) + 'static,\n    ) {\n        App::<Vertex>::new(AppConfig::default().title(title).fullscreen(true))\n            .run(Self::default().update(callback));\n    }\n\n    pub fn simple_fullscreen_scaled(\n        title: impl ToString,\n        scaling: CoordsMappingScaling,\n        callback: impl FnMut(&mut AppControl) + 'static,\n    ) {\n        App::<Vertex>::new(AppConfig::default().title(title).fullscreen(true)).run(\n            Self::default()\n                .coords_mapping_scaling(scaling)\n                .update(callback),\n        );\n    }\n\n    pub fn test_frame<F: FnMut()>(f: F) -> ImmediateAppCycleFrameRunner<F> {\n        ImmediateAppCycleFrameRunner(f)\n    }\n\n    pub fn update(mut self, callback: impl FnMut(&mut AppControl) + 'static) -> Self {\n        let mut callback = Box::new(callback);\n        let context = ImmediateContext::default();\n        self.shared.on_update = Some(Box::new(move |application, control| {\n            raui_immediate::reset();\n            let widgets = make_widgets(&context, || {\n                callback(control);\n            });\n            application.apply(make_widget!(content_box).listed_slots(widgets));\n        }));\n        self\n    }\n\n    pub fn redraw(\n        mut self,\n        f: impl FnMut(f32, &mut Graphics<Vertex>, &mut TextRenderer<Color>, &mut AppControl) + 'static,\n    ) -> Self {\n        self.shared.on_redraw = Some(Box::new(f));\n        self\n    }\n\n    pub fn event(\n        mut self,\n        f: impl FnMut(&mut Application, Event<()>, &mut Window, &mut DefaultInteractionsEngine) -> bool\n        + 'static,\n    ) -> Self {\n        self.shared.on_event = Some(Box::new(f));\n        self\n    }\n\n    pub fn setup(mut self, mut f: impl FnMut(&mut Application)) -> Self {\n        f(&mut self.shared.application);\n        self\n    }\n\n    pub fn setup_interactions(mut self, mut f: impl FnMut(&mut AppInteractionsEngine)) -> Self {\n        f(&mut self.shared.interactions);\n        self\n    }\n\n    pub fn coords_mapping_scaling(mut self, value: CoordsMappingScaling) -> Self {\n        self.shared.coords_mapping_scaling = value;\n        self\n    }\n}\n\nimpl AppState<Vertex> for ImmediateApp {\n    fn on_init(&mut self, graphics: &mut Graphics<Vertex>, _: &mut AppControl) {\n        self.shared.init(graphics);\n    }\n\n    fn on_redraw(&mut self, graphics: &mut Graphics<Vertex>, control: &mut AppControl) {\n        self.shared.redraw(graphics, control);\n    }\n\n    fn on_event(&mut self, event: Event<()>, window: &mut Window) -> bool {\n        self.shared.event(event, window)\n    }\n}\n\npub struct ImmediateAppCycleFrameRunner<F: FnMut()>(F);\n\nimpl<F: FnMut()> AppCycleFrameRunner<ImmediateContext> for ImmediateAppCycleFrameRunner<F> {\n    fn run_frame(mut self, tester: &mut AppCycleTester<ImmediateContext>) {\n        raui_immediate::reset();\n        let widgets = make_widgets(&tester.user_data, || {\n            (self.0)();\n        });\n        tester\n            .application\n            .apply(make_widget!(content_box).listed_slots(widgets));\n    }\n}\n"
  },
  {
    "path": "crates/app/src/app/mod.rs",
    "content": "pub mod declarative;\npub mod immediate;\npub mod retained;\n\nuse crate::{\n    TesselateToGraphics, Vertex, asset_manager::AssetsManager, interactions::AppInteractionsEngine,\n    render_worker::RenderWorkersViewModel, text_measurements::AppTextMeasurementsEngine,\n};\nuse glutin::{\n    event::{ElementState, Event, VirtualKeyCode, WindowEvent},\n    window::Window,\n};\nuse raui_core::{\n    application::Application,\n    interactive::default_interactions_engine::DefaultInteractionsEngine,\n    layout::{CoordsMapping, CoordsMappingScaling, default_layout_engine::DefaultLayoutEngine},\n    view_model::ViewModel,\n    widget::utils::{Color, Rect},\n};\nuse raui_tesselate_renderer::{TesselateRenderer, TessselateRendererDebug};\nuse spitfire_fontdue::TextRenderer;\nuse spitfire_glow::{\n    app::AppControl,\n    graphics::{Graphics, GraphicsBatch, Shader, Texture},\n    renderer::{GlowTextureFormat, GlowUniformValue},\n};\nuse std::{collections::HashMap, time::Instant};\n\npub use spitfire_glow::app::{App, AppConfig};\n\n#[cfg(debug_assertions)]\nconst DEBUG_VERTEX: &str = r#\"#version 300 es\n    layout(location = 0) in vec2 a_position;\n    out vec4 v_color;\n    uniform mat4 u_projection_view;\n\n    void main() {\n        gl_Position = u_projection_view * vec4(a_position, 0.0, 1.0);\n    }\n    \"#;\n\n#[cfg(debug_assertions)]\nconst DEBUG_FRAGMENT: &str = r#\"#version 300 es\n    precision highp float;\n    precision highp int;\n    out vec4 o_color;\n    uniform float u_time;\n\n    vec3 hsv2rgb(vec3 c) {\n        vec4 K = vec4(1.0, 2.0/3.0, 1.0/3.0, 3.0);\n        vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);\n        return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);\n    }\n\n    void main() {\n        vec2 pixel = floor(gl_FragCoord.xy);\n        float hue = fract((floor(pixel.x) + floor(pixel.y)) * 0.01 + u_time);\n        o_color = vec4(hsv2rgb(vec3(hue, 1.0, 1.0)), 1.0);\n    }\n    \"#;\n\nmacro_rules! hash_map {\n    ($($key:ident => $value:expr),* $(,)?) => {{\n        let mut result = HashMap::default();\n        $(\n            result.insert(stringify!($key).into(), $value);\n        )*\n        result\n    }};\n}\n\npub(crate) struct SharedApp {\n    #[allow(clippy::type_complexity)]\n    on_update: Option<Box<dyn FnMut(&mut Application, &mut AppControl)>>,\n    /// fn(delta time, graphics interface)\n    #[allow(clippy::type_complexity)]\n    on_redraw: Option<\n        Box<dyn FnMut(f32, &mut Graphics<Vertex>, &mut TextRenderer<Color>, &mut AppControl)>,\n    >,\n    #[allow(clippy::type_complexity)]\n    on_event: Option<\n        Box<\n            dyn FnMut(\n                &mut Application,\n                Event<()>,\n                &mut Window,\n                &mut DefaultInteractionsEngine,\n            ) -> bool,\n        >,\n    >,\n    application: Application,\n    interactions: AppInteractionsEngine,\n    text_renderer: TextRenderer<Color>,\n    timer: Instant,\n    time: f32,\n    assets: AssetsManager,\n    coords_mapping: CoordsMapping,\n    pub coords_mapping_scaling: CoordsMappingScaling,\n    missing_texutre: Option<Texture>,\n    glyphs_texture: Option<Texture>,\n    colored_shader: Option<Shader>,\n    textured_shader: Option<Shader>,\n    text_shader: Option<Shader>,\n    #[cfg(debug_assertions)]\n    debug_shader: Option<Shader>,\n    #[cfg(debug_assertions)]\n    pub show_raui_aabb_mode: u8,\n    #[cfg(debug_assertions)]\n    pub show_raui_aabb_key: VirtualKeyCode,\n    #[cfg(debug_assertions)]\n    pub print_raui_tree_key: VirtualKeyCode,\n    #[cfg(debug_assertions)]\n    pub print_raui_layout_key: VirtualKeyCode,\n    #[cfg(debug_assertions)]\n    pub print_raui_interactions_key: VirtualKeyCode,\n}\n\nimpl Default for SharedApp {\n    fn default() -> Self {\n        let mut application = Application::default();\n        application.setup(raui_core::widget::setup);\n        application.setup(raui_material::setup);\n        application.view_models.insert(\n            RenderWorkersViewModel::VIEW_MODEL.to_owned(),\n            ViewModel::new(RenderWorkersViewModel::default(), Default::default()),\n        );\n        Self {\n            on_update: None,\n            on_redraw: None,\n            on_event: None,\n            application,\n            interactions: Default::default(),\n            text_renderer: TextRenderer::new(1024, 1024),\n            timer: Instant::now(),\n            time: 0.0,\n            assets: Default::default(),\n            coords_mapping: Default::default(),\n            coords_mapping_scaling: Default::default(),\n            missing_texutre: None,\n            glyphs_texture: None,\n            colored_shader: None,\n            textured_shader: None,\n            text_shader: None,\n            #[cfg(debug_assertions)]\n            debug_shader: None,\n            #[cfg(debug_assertions)]\n            show_raui_aabb_mode: 0,\n            #[cfg(debug_assertions)]\n            show_raui_aabb_key: VirtualKeyCode::F9,\n            #[cfg(debug_assertions)]\n            print_raui_tree_key: VirtualKeyCode::F10,\n            #[cfg(debug_assertions)]\n            print_raui_layout_key: VirtualKeyCode::F11,\n            #[cfg(debug_assertions)]\n            print_raui_interactions_key: VirtualKeyCode::F12,\n        }\n    }\n}\n\nimpl SharedApp {\n    fn init(&mut self, graphics: &mut Graphics<Vertex>) {\n        self.missing_texutre = Some(graphics.pixel_texture([255, 255, 255]).unwrap());\n        self.glyphs_texture = Some(graphics.pixel_texture([0, 0, 0]).unwrap());\n        self.colored_shader = Some(\n            graphics\n                .shader(Shader::COLORED_VERTEX_2D, Shader::PASS_FRAGMENT)\n                .unwrap(),\n        );\n        self.textured_shader = Some(\n            graphics\n                .shader(Shader::TEXTURED_VERTEX_2D, Shader::TEXTURED_FRAGMENT)\n                .unwrap(),\n        );\n        self.text_shader = Some(\n            graphics\n                .shader(Shader::TEXT_VERTEX, Shader::TEXT_FRAGMENT)\n                .unwrap(),\n        );\n        #[cfg(debug_assertions)]\n        {\n            self.debug_shader = Some(graphics.shader(DEBUG_VERTEX, DEBUG_FRAGMENT).unwrap());\n        }\n    }\n\n    fn redraw(&mut self, graphics: &mut Graphics<Vertex>, control: &mut AppControl) {\n        let elapsed = self.timer.elapsed();\n        self.timer = Instant::now();\n        self.time += elapsed.as_secs_f32();\n        if let Some(callback) = self.on_update.as_mut() {\n            callback(&mut self.application, control);\n        }\n        self.text_renderer.clear();\n        if let Some(callback) = self.on_redraw.as_mut() {\n            callback(\n                elapsed.as_secs_f32(),\n                graphics,\n                &mut self.text_renderer,\n                control,\n            );\n        }\n        {\n            self.application\n                .view_models\n                .get_mut(RenderWorkersViewModel::VIEW_MODEL)\n                .unwrap()\n                .write::<RenderWorkersViewModel>()\n                .unwrap()\n                .maintain(\n                    graphics,\n                    &mut self.assets,\n                    self.colored_shader.as_ref().unwrap(),\n                    self.textured_shader.as_ref().unwrap(),\n                    self.text_shader.as_ref().unwrap(),\n                );\n        }\n        self.assets.maintain();\n        self.application.animations_delta_time = elapsed.as_secs_f32();\n        self.coords_mapping = CoordsMapping::new_scaling(\n            Rect {\n                left: 0.0,\n                right: graphics.state.main_camera.screen_size.x,\n                top: 0.0,\n                bottom: graphics.state.main_camera.screen_size.y,\n            },\n            self.coords_mapping_scaling,\n        );\n        if self.application.process() {\n            self.assets.load(self.application.rendered_tree(), graphics);\n            let mut layout_engine = DefaultLayoutEngine::new(AppTextMeasurementsEngine {\n                assets: &self.assets,\n            });\n            let _ = self\n                .application\n                .layout(&self.coords_mapping, &mut layout_engine);\n        } else {\n            self.assets.load(self.application.rendered_tree(), graphics);\n        }\n        let _ = self.application.interact(&mut self.interactions);\n        self.application.consume_signals();\n        let matrix = graphics\n            .state\n            .main_camera\n            .world_projection_matrix()\n            .into_col_array();\n        graphics.state.stream.batch_end();\n        for shader in [\n            self.colored_shader.clone(),\n            self.textured_shader.clone(),\n            self.text_shader.clone(),\n            #[cfg(debug_assertions)]\n            self.debug_shader.clone(),\n        ] {\n            graphics.state.stream.batch(GraphicsBatch {\n                shader,\n                uniforms: hash_map! {\n                    u_image => GlowUniformValue::I1(0),\n                    u_projection_view => GlowUniformValue::M4(matrix),\n                    u_time => GlowUniformValue::F1(self.time),\n                },\n                ..Default::default()\n            });\n            graphics.state.stream.batch_end();\n        }\n        let mut converter = TesselateToGraphics {\n            colored_shader: self.colored_shader.as_ref().unwrap(),\n            textured_shader: self.textured_shader.as_ref().unwrap(),\n            text_shader: self.text_shader.as_ref().unwrap(),\n            #[cfg(debug_assertions)]\n            debug_shader: self.debug_shader.as_ref(),\n            glyphs_texture: self.glyphs_texture.as_ref().unwrap(),\n            missing_texture: self.missing_texutre.as_ref().unwrap(),\n            assets: &self.assets,\n            clip_stack: Vec::with_capacity(64),\n            viewport_height: graphics.state.main_camera.screen_size.y as _,\n            projection_view_matrix: matrix,\n        };\n        let mut renderer = TesselateRenderer::new(\n            &self.assets,\n            &mut converter,\n            &mut graphics.state.stream,\n            &mut self.text_renderer,\n            if cfg!(debug_assertions) {\n                match self.show_raui_aabb_mode {\n                    0 => None,\n                    1 => Some(TessselateRendererDebug {\n                        render_non_visual_nodes: false,\n                    }),\n                    2 => Some(TessselateRendererDebug {\n                        render_non_visual_nodes: true,\n                    }),\n                    _ => unreachable!(),\n                }\n            } else {\n                None\n            },\n        );\n        let _ = self.application.render(&self.coords_mapping, &mut renderer);\n        let [w, h, d] = self.text_renderer.atlas_size();\n        self.glyphs_texture.as_mut().unwrap().upload(\n            w as _,\n            h as _,\n            d as _,\n            GlowTextureFormat::Monochromatic,\n            Some(self.text_renderer.image()),\n        );\n    }\n\n    fn event(&mut self, event: Event<()>, window: &mut Window) -> bool {\n        self.interactions.event(&event, &self.coords_mapping);\n        if let Event::WindowEvent {\n            event: WindowEvent::Resized(_),\n            ..\n        } = &event\n        {\n            self.application.mark_dirty();\n        }\n        #[cfg(debug_assertions)]\n        if let Event::WindowEvent {\n            event: WindowEvent::KeyboardInput { input, .. },\n            ..\n        } = &event\n            && input.state == ElementState::Pressed\n            && let Some(key) = input.virtual_keycode\n        {\n            if key == self.show_raui_aabb_key {\n                self.show_raui_aabb_mode = (self.show_raui_aabb_mode + 1) % 3;\n                println!(\n                    \"* SHOW RAUI LAYOUT AABB MODE: {:#?}\",\n                    self.show_raui_aabb_mode\n                );\n            } else if key == self.print_raui_tree_key {\n                println!(\"* RAUI TREE: {:#?}\", self.application.rendered_tree());\n            } else if key == self.print_raui_layout_key {\n                println!(\"* RAUI LAYOUT: {:#?}\", self.application.layout_data());\n            } else if key == self.print_raui_interactions_key {\n                println!(\"* RAUI INTERACTIONS: {:#?}\", self.interactions);\n            }\n        }\n        self.on_event\n            .as_mut()\n            .map(|callback| {\n                callback(\n                    &mut self.application,\n                    event,\n                    window,\n                    &mut self.interactions.engine,\n                )\n            })\n            .unwrap_or(true)\n    }\n}\n"
  },
  {
    "path": "crates/app/src/app/retained.rs",
    "content": "use crate::{Vertex, app::SharedApp, interactions::AppInteractionsEngine};\nuse glutin::{event::Event, window::Window};\nuse raui_core::{\n    application::{Application, ChangeNotifier},\n    interactive::default_interactions_engine::DefaultInteractionsEngine,\n    layout::CoordsMappingScaling,\n    widget::utils::Color,\n};\nuse raui_retained::{View, ViewState};\nuse spitfire_fontdue::TextRenderer;\nuse spitfire_glow::{\n    app::{App, AppConfig, AppControl, AppState},\n    graphics::Graphics,\n};\n\npub struct RetainedApp<T: ViewState> {\n    shared: SharedApp,\n    root: Option<View<T>>,\n}\n\nimpl<T: ViewState> Default for RetainedApp<T> {\n    fn default() -> Self {\n        Self {\n            shared: Default::default(),\n            root: None,\n        }\n    }\n}\n\nimpl<T: ViewState> RetainedApp<T> {\n    pub fn simple(title: impl ToString, producer: impl FnMut(ChangeNotifier) -> View<T>) {\n        App::<Vertex>::new(AppConfig::default().title(title)).run(Self::default().tree(producer));\n    }\n\n    pub fn simple_scaled(\n        title: impl ToString,\n        scaling: CoordsMappingScaling,\n        producer: impl FnMut(ChangeNotifier) -> View<T>,\n    ) {\n        App::<Vertex>::new(AppConfig::default().title(title)).run(\n            Self::default()\n                .coords_mapping_scaling(scaling)\n                .tree(producer),\n        );\n    }\n\n    pub fn simple_fullscreen(\n        title: impl ToString,\n        producer: impl FnMut(ChangeNotifier) -> View<T>,\n    ) {\n        App::<Vertex>::new(AppConfig::default().title(title).fullscreen(true))\n            .run(Self::default().tree(producer));\n    }\n\n    pub fn simple_fullscreen_scaled(\n        title: impl ToString,\n        scaling: CoordsMappingScaling,\n        producer: impl FnMut(ChangeNotifier) -> View<T>,\n    ) {\n        App::<Vertex>::new(AppConfig::default().title(title).fullscreen(true)).run(\n            Self::default()\n                .coords_mapping_scaling(scaling)\n                .tree(producer),\n        );\n    }\n\n    pub fn redraw(\n        mut self,\n        f: impl FnMut(f32, &mut Graphics<Vertex>, &mut TextRenderer<Color>, &mut AppControl) + 'static,\n    ) -> Self {\n        self.shared.on_redraw = Some(Box::new(f));\n        self\n    }\n\n    pub fn event(\n        mut self,\n        f: impl FnMut(&mut Application, Event<()>, &mut Window, &mut DefaultInteractionsEngine) -> bool\n        + 'static,\n    ) -> Self {\n        self.shared.on_event = Some(Box::new(f));\n        self\n    }\n\n    pub fn setup(mut self, mut f: impl FnMut(&mut Application)) -> Self {\n        f(&mut self.shared.application);\n        self\n    }\n\n    pub fn setup_interactions(mut self, mut f: impl FnMut(&mut AppInteractionsEngine)) -> Self {\n        f(&mut self.shared.interactions);\n        self\n    }\n\n    pub fn tree(mut self, mut producer: impl FnMut(ChangeNotifier) -> View<T>) -> Self {\n        let root = producer(self.shared.application.notifier());\n        self.shared.application.apply(root.component().key(\"root\"));\n        self.root = Some(root);\n        self\n    }\n\n    pub fn coords_mapping_scaling(mut self, value: CoordsMappingScaling) -> Self {\n        self.shared.coords_mapping_scaling = value;\n        self\n    }\n}\n\nimpl<T: ViewState> AppState<Vertex> for RetainedApp<T> {\n    fn on_init(&mut self, graphics: &mut Graphics<Vertex>, _: &mut AppControl) {\n        self.shared.init(graphics);\n    }\n\n    fn on_redraw(&mut self, graphics: &mut Graphics<Vertex>, control: &mut AppControl) {\n        self.shared.redraw(graphics, control);\n    }\n\n    fn on_event(&mut self, event: Event<()>, window: &mut Window) -> bool {\n        self.shared.event(event, window)\n    }\n}\n"
  },
  {
    "path": "crates/app/src/asset_manager.rs",
    "content": "use crate::Vertex;\nuse fontdue::Font;\nuse image::EncodableLayout;\nuse raui_core::widget::{\n    unit::{WidgetUnit, image::ImageBoxMaterial, portal::PortalBoxSlot},\n    utils::{Rect, Vec2},\n};\nuse raui_tesselate_renderer::TesselateResourceProvider;\nuse serde::{Deserialize, Serialize};\nuse spitfire_glow::{\n    graphics::{Graphics, Shader, Texture},\n    renderer::GlowTextureFormat,\n};\nuse std::{collections::HashMap, path::PathBuf};\n\n#[derive(Serialize, Deserialize)]\npub struct AssetAtlasRegion {\n    x: u32,\n    y: u32,\n    width: u32,\n    height: u32,\n}\n\n#[derive(Serialize, Deserialize)]\nstruct AssetAtlas {\n    image: PathBuf,\n    regions: HashMap<String, AssetAtlasRegion>,\n}\n\npub(crate) struct AssetTexture {\n    pub(crate) texture: Texture,\n    /// {name: uvs}\n    regions: HashMap<String, Rect>,\n    frames_left: usize,\n    forever_alive: bool,\n}\n\npub(crate) struct AssetFont {\n    hash: usize,\n    frames_left: usize,\n    forever_alive: bool,\n}\n\npub(crate) struct AssetShader {\n    pub(crate) shader: Shader,\n    frames_left: usize,\n    forever_alive: bool,\n}\n\npub(crate) struct AssetsManager {\n    pub frames_alive: usize,\n    pub(crate) root_path: PathBuf,\n    pub(crate) textures: HashMap<String, AssetTexture>,\n    pub(crate) font_map: HashMap<String, AssetFont>,\n    pub(crate) fonts: Vec<Font>,\n    pub(crate) shaders: HashMap<String, AssetShader>,\n}\n\nimpl Default for AssetsManager {\n    fn default() -> Self {\n        Self::new(\"./\", 1024)\n    }\n}\n\nimpl AssetsManager {\n    fn new(root_path: impl Into<PathBuf>, frames_alive: usize) -> Self {\n        Self {\n            frames_alive,\n            root_path: root_path.into(),\n            textures: Default::default(),\n            font_map: Default::default(),\n            fonts: Default::default(),\n            shaders: Default::default(),\n        }\n    }\n\n    pub(crate) fn maintain(&mut self) {\n        let to_remove = self\n            .textures\n            .iter_mut()\n            .filter(|(_, texture)| !texture.forever_alive)\n            .filter_map(|(id, texture)| {\n                if texture.frames_left > 0 {\n                    texture.frames_left -= 1;\n                    None\n                } else {\n                    Some(id.to_owned())\n                }\n            })\n            .collect::<Vec<_>>();\n        for id in to_remove {\n            self.textures.remove(&id);\n        }\n\n        let to_remove = self\n            .font_map\n            .iter_mut()\n            .filter(|(_, font)| !font.forever_alive)\n            .filter_map(|(id, font)| {\n                if font.frames_left > 0 {\n                    font.frames_left -= 1;\n                    None\n                } else {\n                    Some(id.to_owned())\n                }\n            })\n            .collect::<Vec<_>>();\n        for id in to_remove {\n            let hash = self.font_map.remove(&id).unwrap().hash;\n            if let Some(index) = self.fonts.iter().position(|font| font.file_hash() == hash) {\n                self.fonts.swap_remove(index);\n            }\n        }\n\n        let to_remove = self\n            .shaders\n            .iter_mut()\n            .filter(|(_, shader)| !shader.forever_alive)\n            .filter_map(|(id, shader)| {\n                if shader.frames_left > 0 {\n                    shader.frames_left -= 1;\n                    None\n                } else {\n                    Some(id.to_owned())\n                }\n            })\n            .collect::<Vec<_>>();\n        for id in to_remove {\n            self.shaders.remove(&id);\n        }\n    }\n\n    pub(crate) fn load(&mut self, node: &WidgetUnit, graphics: &Graphics<Vertex>) {\n        match node {\n            WidgetUnit::None => {}\n            WidgetUnit::AreaBox(node) => {\n                self.load(&node.slot, graphics);\n            }\n            WidgetUnit::PortalBox(node) => match &*node.slot {\n                PortalBoxSlot::Slot(node) => {\n                    self.load(node, graphics);\n                }\n                PortalBoxSlot::ContentItem(node) => {\n                    self.load(&node.slot, graphics);\n                }\n                PortalBoxSlot::FlexItem(node) => {\n                    self.load(&node.slot, graphics);\n                }\n                PortalBoxSlot::GridItem(node) => {\n                    self.load(&node.slot, graphics);\n                }\n            },\n            WidgetUnit::ContentBox(node) => {\n                for item in &node.items {\n                    self.load(&item.slot, graphics);\n                }\n            }\n            WidgetUnit::FlexBox(node) => {\n                for item in &node.items {\n                    self.load(&item.slot, graphics);\n                }\n            }\n            WidgetUnit::GridBox(node) => {\n                for item in &node.items {\n                    self.load(&item.slot, graphics);\n                }\n            }\n            WidgetUnit::SizeBox(node) => {\n                self.load(&node.slot, graphics);\n            }\n            WidgetUnit::ImageBox(node) => match &node.material {\n                ImageBoxMaterial::Image(image) => {\n                    let id = Self::parse_image_id(&image.id).0;\n                    self.try_load_image(id, graphics, false);\n                }\n                ImageBoxMaterial::Procedural(procedural) => {\n                    for id in &procedural.images {\n                        self.try_load_image(id, graphics, false);\n                    }\n                    if !procedural.id.is_empty() {\n                        self.try_load_shader(&procedural.id, graphics, false);\n                    }\n                }\n                _ => {}\n            },\n            WidgetUnit::TextBox(node) => {\n                self.try_load_font(&node.font.name, false);\n            }\n        }\n    }\n\n    pub(crate) fn parse_image_id(id: &str) -> (&str, Option<&str>) {\n        match id.find('@') {\n            Some(index) => (&id[..index], Some(&id[(index + b\"@\".len())..])),\n            None => (id, None),\n        }\n    }\n\n    pub(crate) fn add_texture(&mut self, id: impl ToString, texture: Texture) {\n        self.textures.insert(\n            id.to_string(),\n            AssetTexture {\n                texture,\n                regions: Default::default(),\n                frames_left: self.frames_alive,\n                forever_alive: true,\n            },\n        );\n    }\n\n    pub(crate) fn remove_texture(&mut self, id: impl ToString) {\n        self.textures.remove(&id.to_string());\n    }\n\n    // pub(crate) fn add_shader(&mut self, id: impl ToString, shader: Shader) {\n    //     self.shaders.insert(\n    //         id.to_string(),\n    //         AssetShader {\n    //             shader,\n    //             frames_left: self.frames_alive,\n    //             forever_alive: true,\n    //         },\n    //     );\n    // }\n\n    // pub(crate) fn remove_shader(&mut self, id: impl ToString) {\n    //     self.shaders.remove(&id.to_string());\n    // }\n\n    // pub(crate) fn add_font(&mut self, id: impl ToString, font: Font) {\n    //     self.font_map.insert(\n    //         id.to_string(),\n    //         AssetFont {\n    //             hash: font.file_hash(),\n    //             frames_left: self.frames_alive,\n    //             forever_alive: true,\n    //         },\n    //     );\n    //     self.fonts.push(font);\n    // }\n\n    // pub(crate) fn remove_font(&mut self, id: impl ToString) {\n    //     if let Some(font) = self.font_map.remove(&id.to_string()) {\n    //         if let Some(index) = self.fonts.iter().position(|f| f.file_hash() == font.hash) {\n    //             self.fonts.swap_remove(index);\n    //         }\n    //     }\n    // }\n\n    fn try_load_image(&mut self, id: &str, graphics: &Graphics<Vertex>, forever_alive: bool) {\n        if let Some(texture) = self.textures.get_mut(id) {\n            texture.frames_left = self.frames_alive;\n        } else {\n            let mut path = self.root_path.join(id);\n            match path.extension().and_then(|ext| ext.to_str()).unwrap_or(\"\") {\n                \"toml\" => {\n                    let content = match std::fs::read_to_string(&path) {\n                        Ok(content) => content,\n                        _ => {\n                            eprintln!(\"Could not load image atlas file: {path:?}\");\n                            return;\n                        }\n                    };\n                    let atlas = match toml::from_str::<AssetAtlas>(&content) {\n                        Ok(atlas) => atlas,\n                        _ => {\n                            eprintln!(\"Could not parse image atlas file: {path:?}\");\n                            return;\n                        }\n                    };\n                    path.pop();\n                    let path = path.join(atlas.image);\n                    let image = match image::open(&path) {\n                        Ok(image) => image.to_rgba8(),\n                        _ => {\n                            eprintln!(\"Could not load image file: {path:?}\");\n                            return;\n                        }\n                    };\n                    let texture = match graphics.texture(\n                        image.width(),\n                        image.height(),\n                        1,\n                        GlowTextureFormat::Rgba,\n                        Some(image.as_bytes()),\n                    ) {\n                        Ok(texture) => texture,\n                        _ => {\n                            eprintln!(\"Could not create texture for image file: {path:?}\");\n                            return;\n                        }\n                    };\n                    let regions = atlas\n                        .regions\n                        .into_iter()\n                        .map(|(name, region)| {\n                            let left = region.x as f32 / image.width() as f32;\n                            let right = (region.x + region.width) as f32 / image.width() as f32;\n                            let top = region.y as f32 / image.height() as f32;\n                            let bottom = (region.y + region.height) as f32 / image.height() as f32;\n                            (\n                                name,\n                                Rect {\n                                    left,\n                                    right,\n                                    top,\n                                    bottom,\n                                },\n                            )\n                        })\n                        .collect();\n                    self.textures.insert(\n                        id.to_owned(),\n                        AssetTexture {\n                            texture,\n                            regions,\n                            frames_left: self.frames_alive,\n                            forever_alive,\n                        },\n                    );\n                }\n                _ => {\n                    let image = match image::open(&path) {\n                        Ok(image) => image.to_rgba8(),\n                        _ => {\n                            eprintln!(\"Could not load image file: {path:?}\");\n                            return;\n                        }\n                    };\n                    let texture = match graphics.texture(\n                        image.width(),\n                        image.height(),\n                        1,\n                        GlowTextureFormat::Rgba,\n                        Some(image.as_bytes()),\n                    ) {\n                        Ok(texture) => texture,\n                        _ => {\n                            eprintln!(\"Could not create texture for image file: {path:?}\");\n                            return;\n                        }\n                    };\n                    self.textures.insert(\n                        id.to_owned(),\n                        AssetTexture {\n                            texture,\n                            regions: Default::default(),\n                            frames_left: self.frames_alive,\n                            forever_alive,\n                        },\n                    );\n                }\n            }\n        }\n    }\n\n    fn try_load_font(&mut self, id: &str, forever_alive: bool) {\n        if let Some(font) = self.font_map.get_mut(id) {\n            font.frames_left = self.frames_alive;\n        } else {\n            let path = self.root_path.join(id);\n            let content = match std::fs::read(&path) {\n                Ok(content) => content,\n                _ => {\n                    eprintln!(\"Could not load font file: {path:?}\");\n                    return;\n                }\n            };\n            let font = match Font::from_bytes(content, Default::default()) {\n                Ok(font) => font,\n                _ => return,\n            };\n            self.font_map.insert(\n                id.to_owned(),\n                AssetFont {\n                    hash: font.file_hash(),\n                    frames_left: self.frames_alive,\n                    forever_alive,\n                },\n            );\n            self.fonts.push(font);\n        }\n    }\n\n    fn try_load_shader(&mut self, id: &str, graphics: &Graphics<Vertex>, forever_alive: bool) {\n        if let Some(shader) = self.shaders.get_mut(id) {\n            shader.frames_left = self.frames_alive;\n        } else {\n            let shader = match id {\n                \"@pass\" => match graphics.shader(Shader::PASS_VERTEX_2D, Shader::PASS_FRAGMENT) {\n                    Ok(shader) => shader,\n                    _ => {\n                        eprintln!(\"Could not create shader for: {id:?}\");\n                        return;\n                    }\n                },\n                \"@colored\" => {\n                    match graphics.shader(Shader::COLORED_VERTEX_2D, Shader::PASS_FRAGMENT) {\n                        Ok(shader) => shader,\n                        _ => {\n                            eprintln!(\"Could not create shader for: {id:?}\");\n                            return;\n                        }\n                    }\n                }\n                \"@textured\" => {\n                    match graphics.shader(Shader::TEXTURED_VERTEX_2D, Shader::TEXTURED_FRAGMENT) {\n                        Ok(shader) => shader,\n                        _ => {\n                            eprintln!(\"Could not create shader for: {id:?}\");\n                            return;\n                        }\n                    }\n                }\n                _ => {\n                    let path = self.root_path.join(format!(\"{id}.vs\"));\n                    let vertex = match std::fs::read_to_string(&path) {\n                        Ok(content) => content,\n                        _ => {\n                            eprintln!(\"Could not load vertex shader file: {path:?}\");\n                            return;\n                        }\n                    };\n                    let path = self.root_path.join(format!(\"{id}.fs\"));\n                    let fragment = match std::fs::read_to_string(&path) {\n                        Ok(content) => content,\n                        _ => {\n                            eprintln!(\"Could not load fragment shader file: {path:?}\");\n                            return;\n                        }\n                    };\n                    match graphics.shader(&vertex, &fragment) {\n                        Ok(shader) => shader,\n                        _ => {\n                            eprintln!(\"Could not create shader for: {id:?}\");\n                            return;\n                        }\n                    }\n                }\n            };\n            self.shaders.insert(\n                id.to_owned(),\n                AssetShader {\n                    shader,\n                    frames_left: self.frames_alive,\n                    forever_alive,\n                },\n            );\n        }\n    }\n}\n\nimpl TesselateResourceProvider for AssetsManager {\n    fn image_id_and_uv_and_size_by_atlas_id(&self, id: &str) -> Option<(String, Rect, Vec2)> {\n        let (id, region) = Self::parse_image_id(id);\n        let texture = self.textures.get(id)?;\n        let uvs = region\n            .and_then(|region| texture.regions.get(region))\n            .copied()\n            .unwrap_or(Rect {\n                left: 0.0,\n                right: 1.0,\n                top: 0.0,\n                bottom: 1.0,\n            });\n        let size = Vec2 {\n            x: texture.texture.width() as _,\n            y: texture.texture.height() as _,\n        };\n        Some((id.to_owned(), uvs, size))\n    }\n\n    fn fonts(&self) -> &[Font] {\n        &self.fonts\n    }\n\n    fn font_index_by_id(&self, id: &str) -> Option<usize> {\n        let hash = self.font_map.get(id)?.hash;\n        self.fonts.iter().position(|font| font.file_hash() == hash)\n    }\n}\n"
  },
  {
    "path": "crates/app/src/components/canvas.rs",
    "content": "use crate::render_worker::{\n    RenderWorkerDescriptor, RenderWorkerTaskContext, RenderWorkersViewModel,\n};\nuse raui_core::{\n    MessageData, Prefab, PropsData, make_widget,\n    messenger::MessageData,\n    pre_hooks,\n    props::PropsData,\n    widget::{\n        component::{\n            ResizeListenerSignal,\n            image_box::{ImageBoxProps, image_box},\n            use_resize_listener,\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::area::AreaBoxNode,\n        utils::Color,\n    },\n};\nuse serde::{Deserialize, Serialize};\nuse spitfire_glow::renderer::GlowTextureFormat;\nuse std::sync::Arc;\n\n#[derive(PropsData, Debug, Default, Copy, Clone, Serialize, Deserialize)]\npub struct CanvasProps {\n    #[serde(default)]\n    pub color: Color,\n    #[serde(default)]\n    pub clear: bool,\n}\n\n#[derive(MessageData, Debug, Clone)]\npub struct RequestCanvasRedrawMessage;\n\n#[derive(MessageData, Clone)]\npub enum DrawOnCanvasMessage {\n    Function(fn(RenderWorkerTaskContext)),\n    #[allow(clippy::type_complexity)]\n    Generator(Arc<dyn Fn() -> Box<dyn FnOnce(RenderWorkerTaskContext)> + Send + Sync>),\n}\n\nimpl DrawOnCanvasMessage {\n    pub fn function(callback: fn(RenderWorkerTaskContext)) -> Self {\n        Self::Function(callback)\n    }\n\n    pub fn generator(\n        callback: impl Fn() -> Box<dyn FnOnce(RenderWorkerTaskContext)> + Send + Sync + 'static,\n    ) -> Self {\n        Self::Generator(Arc::new(callback))\n    }\n}\n\nimpl std::fmt::Debug for DrawOnCanvasMessage {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"DrawOnCanvasMessage\")\n            .finish_non_exhaustive()\n    }\n}\n\npub fn use_canvas(ctx: &mut WidgetContext) {\n    ctx.life_cycle.mount(|mut ctx| {\n        let props = ctx.props.read_cloned_or_default::<CanvasProps>();\n        let mut workers = ctx\n            .view_models\n            .view_model_mut(RenderWorkersViewModel::VIEW_MODEL)\n            .unwrap()\n            .write::<RenderWorkersViewModel>()\n            .unwrap();\n        workers.add_worker(RenderWorkerDescriptor {\n            id: ctx.id.to_string(),\n            width: 1,\n            height: 1,\n            format: GlowTextureFormat::Rgba,\n            color: [props.color.r, props.color.g, props.color.b, props.color.a],\n        });\n        ctx.messenger\n            .write(ctx.id.to_owned(), RequestCanvasRedrawMessage);\n    });\n\n    ctx.life_cycle.unmount(|mut ctx| {\n        let mut workers = ctx\n            .view_models\n            .view_model_mut(RenderWorkersViewModel::VIEW_MODEL)\n            .unwrap()\n            .write::<RenderWorkersViewModel>()\n            .unwrap();\n        workers.remove_worker(ctx.id.as_ref());\n    });\n\n    ctx.life_cycle.change(|mut ctx| {\n        let props = ctx.props.read_cloned_or_default::<CanvasProps>();\n        let mut workers = ctx\n            .view_models\n            .view_model_mut(RenderWorkersViewModel::VIEW_MODEL)\n            .unwrap()\n            .write::<RenderWorkersViewModel>()\n            .unwrap();\n        for msg in ctx.messenger.messages {\n            if let Some(ResizeListenerSignal::Change(size)) = msg.as_any().downcast_ref() {\n                workers.add_worker(RenderWorkerDescriptor {\n                    id: ctx.id.to_string(),\n                    width: size.x as u32,\n                    height: size.y as u32,\n                    format: GlowTextureFormat::Rgba,\n                    color: [props.color.r, props.color.g, props.color.b, props.color.a],\n                });\n                ctx.messenger\n                    .write(ctx.id.to_owned(), RequestCanvasRedrawMessage);\n            } else if let Some(msg) = msg.as_any().downcast_ref::<DrawOnCanvasMessage>() {\n                match msg {\n                    DrawOnCanvasMessage::Function(task) => {\n                        workers.schedule_task(ctx.id.as_ref(), props.clear, *task);\n                    }\n                    DrawOnCanvasMessage::Generator(task) => {\n                        workers.schedule_task(ctx.id.as_ref(), props.clear, (*task)());\n                    }\n                }\n            }\n        }\n    });\n}\n\n#[pre_hooks(use_resize_listener, use_canvas)]\npub fn canvas(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext { id, idref, key, .. } = context;\n\n    let content = make_widget!(image_box)\n        .key(key)\n        .maybe_idref(idref.cloned())\n        .with_props(ImageBoxProps::image(id))\n        .into();\n\n    AreaBoxNode {\n        id: id.to_owned(),\n        slot: Box::new(content),\n    }\n    .into()\n}\n"
  },
  {
    "path": "crates/app/src/components/mod.rs",
    "content": "pub mod canvas;\n"
  },
  {
    "path": "crates/app/src/interactions.rs",
    "content": "use glutin::event::{\n    ElementState, Event, ModifiersState, MouseButton, MouseScrollDelta, VirtualKeyCode, WindowEvent,\n};\nuse raui_core::{\n    application::Application,\n    interactive::{\n        InteractionsEngine,\n        default_interactions_engine::{\n            DefaultInteractionsEngine, DefaultInteractionsEngineResult, Interaction, PointerButton,\n        },\n    },\n    layout::CoordsMapping,\n    widget::{\n        component::interactive::navigation::{NavJump, NavScroll, NavSignal, NavTextChange},\n        utils::Vec2,\n    },\n};\n\n#[derive(Debug)]\npub struct AppInteractionsEngine {\n    pub engine: DefaultInteractionsEngine,\n    pub single_scroll_units: Vec2,\n    pointer_position: Vec2,\n    modifiers: ModifiersState,\n}\n\nimpl Default for AppInteractionsEngine {\n    fn default() -> Self {\n        Self::with_capacity(32, 32, 1024, 32, 32, 32, 32, 32, 32)\n    }\n}\n\nimpl AppInteractionsEngine {\n    fn default_single_scroll_units() -> Vec2 {\n        Vec2 { x: 10.0, y: 10.0 }\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    pub fn with_capacity(\n        resize_listeners: usize,\n        relative_layout_listeners: usize,\n        interactions_queue: usize,\n        containers: usize,\n        buttons: usize,\n        text_inputs: usize,\n        scroll_views: usize,\n        tracking: usize,\n        selected_chain: usize,\n    ) -> Self {\n        let mut engine = DefaultInteractionsEngine::with_capacity(\n            resize_listeners,\n            relative_layout_listeners,\n            interactions_queue,\n            containers,\n            buttons,\n            text_inputs,\n            scroll_views,\n            tracking,\n            selected_chain,\n        );\n        engine.deselect_when_no_button_found = true;\n        Self {\n            engine,\n            single_scroll_units: Self::default_single_scroll_units(),\n            pointer_position: Default::default(),\n            modifiers: Default::default(),\n        }\n    }\n\n    pub fn event(&mut self, event: &Event<()>, mapping: &CoordsMapping) {\n        if let Event::WindowEvent { event, .. } = event {\n            match event {\n                WindowEvent::ModifiersChanged(modifiers) => {\n                    self.modifiers = *modifiers;\n                }\n                WindowEvent::ReceivedCharacter(character) => {\n                    self.engine\n                        .interact(Interaction::Navigate(NavSignal::TextChange(\n                            NavTextChange::InsertCharacter(*character),\n                        )));\n                }\n                WindowEvent::CursorMoved { position, .. } => {\n                    self.pointer_position = mapping.real_to_virtual_vec2(\n                        Vec2 {\n                            x: position.x as _,\n                            y: position.y as _,\n                        },\n                        false,\n                    );\n                    self.engine\n                        .interact(Interaction::PointerMove(self.pointer_position));\n                }\n                WindowEvent::MouseWheel { delta, .. } => {\n                    let value = match delta {\n                        MouseScrollDelta::LineDelta(x, y) => Vec2 {\n                            x: -self.single_scroll_units.x * *x,\n                            y: -self.single_scroll_units.y * *y,\n                        },\n                        MouseScrollDelta::PixelDelta(delta) => Vec2 {\n                            x: -delta.x as _,\n                            y: -delta.y as _,\n                        },\n                    };\n                    self.engine\n                        .interact(Interaction::Navigate(NavSignal::Jump(NavJump::Scroll(\n                            NavScroll::Units(value, true),\n                        ))));\n                }\n                WindowEvent::MouseInput { state, button, .. } => match state {\n                    ElementState::Pressed => match button {\n                        MouseButton::Left => {\n                            self.engine.interact(Interaction::PointerDown(\n                                PointerButton::Trigger,\n                                self.pointer_position,\n                            ));\n                        }\n                        MouseButton::Right => {\n                            self.engine.interact(Interaction::PointerDown(\n                                PointerButton::Context,\n                                self.pointer_position,\n                            ));\n                        }\n                        _ => {}\n                    },\n                    ElementState::Released => match button {\n                        MouseButton::Left => {\n                            self.engine.interact(Interaction::PointerUp(\n                                PointerButton::Trigger,\n                                self.pointer_position,\n                            ));\n                        }\n                        MouseButton::Right => {\n                            self.engine.interact(Interaction::PointerUp(\n                                PointerButton::Context,\n                                self.pointer_position,\n                            ));\n                        }\n                        _ => {}\n                    },\n                },\n                WindowEvent::KeyboardInput { input, .. } => {\n                    if input.state == ElementState::Pressed {\n                        if let Some(key) = input.virtual_keycode {\n                            if self.engine.focused_text_input().is_some() {\n                                match key {\n                                    VirtualKeyCode::Left => {\n                                        self.engine.interact(Interaction::Navigate(\n                                            NavSignal::TextChange(NavTextChange::MoveCursorLeft),\n                                        ))\n                                    }\n                                    VirtualKeyCode::Right => {\n                                        self.engine.interact(Interaction::Navigate(\n                                            NavSignal::TextChange(NavTextChange::MoveCursorRight),\n                                        ))\n                                    }\n                                    VirtualKeyCode::Home => {\n                                        self.engine.interact(Interaction::Navigate(\n                                            NavSignal::TextChange(NavTextChange::MoveCursorStart),\n                                        ))\n                                    }\n                                    VirtualKeyCode::End => {\n                                        self.engine.interact(Interaction::Navigate(\n                                            NavSignal::TextChange(NavTextChange::MoveCursorEnd),\n                                        ))\n                                    }\n                                    VirtualKeyCode::Back => {\n                                        self.engine.interact(Interaction::Navigate(\n                                            NavSignal::TextChange(NavTextChange::DeleteLeft),\n                                        ))\n                                    }\n                                    VirtualKeyCode::Delete => {\n                                        self.engine.interact(Interaction::Navigate(\n                                            NavSignal::TextChange(NavTextChange::DeleteRight),\n                                        ))\n                                    }\n                                    VirtualKeyCode::Return | VirtualKeyCode::NumpadEnter => {\n                                        self.engine.interact(Interaction::Navigate(\n                                            NavSignal::TextChange(NavTextChange::NewLine),\n                                        ))\n                                    }\n                                    VirtualKeyCode::Escape => {\n                                        self.engine.interact(Interaction::Navigate(\n                                            NavSignal::FocusTextInput(().into()),\n                                        ));\n                                    }\n                                    _ => {}\n                                }\n                            } else {\n                                match key {\n                                    VirtualKeyCode::Up => {\n                                        self.engine.interact(Interaction::Navigate(NavSignal::Up))\n                                    }\n                                    VirtualKeyCode::Down => {\n                                        self.engine.interact(Interaction::Navigate(NavSignal::Down))\n                                    }\n                                    VirtualKeyCode::Left => {\n                                        if self.modifiers.shift() {\n                                            self.engine\n                                                .interact(Interaction::Navigate(NavSignal::Prev));\n                                        } else {\n                                            self.engine\n                                                .interact(Interaction::Navigate(NavSignal::Left));\n                                        }\n                                    }\n                                    VirtualKeyCode::Right => {\n                                        if self.modifiers.shift() {\n                                            self.engine\n                                                .interact(Interaction::Navigate(NavSignal::Next));\n                                        } else {\n                                            self.engine\n                                                .interact(Interaction::Navigate(NavSignal::Right));\n                                        }\n                                    }\n                                    VirtualKeyCode::Return\n                                    | VirtualKeyCode::NumpadEnter\n                                    | VirtualKeyCode::Space => {\n                                        self.engine.interact(Interaction::Navigate(\n                                            NavSignal::Accept(true),\n                                        ));\n                                    }\n                                    VirtualKeyCode::Escape | VirtualKeyCode::Back => {\n                                        self.engine.interact(Interaction::Navigate(\n                                            NavSignal::Cancel(true),\n                                        ));\n                                    }\n                                    _ => {}\n                                }\n                            }\n                        }\n                    } else if input.state == ElementState::Released\n                        && let Some(key) = input.virtual_keycode\n                        && self.engine.focused_text_input().is_none()\n                    {\n                        match key {\n                            VirtualKeyCode::Return\n                            | VirtualKeyCode::NumpadEnter\n                            | VirtualKeyCode::Space => {\n                                self.engine\n                                    .interact(Interaction::Navigate(NavSignal::Accept(false)));\n                            }\n                            VirtualKeyCode::Escape | VirtualKeyCode::Back => {\n                                self.engine\n                                    .interact(Interaction::Navigate(NavSignal::Cancel(false)));\n                            }\n                            _ => {}\n                        }\n                    }\n                }\n                _ => {}\n            }\n        }\n    }\n}\n\nimpl InteractionsEngine<DefaultInteractionsEngineResult, ()> for AppInteractionsEngine {\n    fn perform_interactions(\n        &mut self,\n        app: &mut Application,\n    ) -> Result<DefaultInteractionsEngineResult, ()> {\n        self.engine.perform_interactions(app)\n    }\n}\n"
  },
  {
    "path": "crates/app/src/lib.rs",
    "content": "pub mod app;\npub(crate) mod asset_manager;\npub mod components;\npub(crate) mod interactions;\npub mod render_worker;\npub(crate) mod text_measurements;\n\nuse crate::{\n    asset_manager::AssetsManager,\n    components::canvas::{CanvasProps, canvas},\n};\nuse bytemuck::{Pod, Zeroable};\nuse raui_core::{\n    application::Application,\n    widget::{FnWidget, utils::Color},\n};\nuse raui_tesselate_renderer::{TesselateBatch, TesselateBatchConverter, TesselateVertex};\nuse spitfire_fontdue::TextVertex;\nuse spitfire_glow::{\n    graphics::{\n        Texture, {GraphicsBatch, Shader},\n    },\n    renderer::{\n        GlowBlending, GlowTextureFiltering, GlowUniformValue, GlowVertexAttrib, GlowVertexAttribs,\n    },\n};\nuse vek::Rect;\n\n#[cfg(not(target_arch = \"wasm32\"))]\npub use glutin::{dpi, event, window};\n#[cfg(target_arch = \"wasm32\")]\npub use winit::{dpi, event, window};\n\npub mod third_party {\n    pub use spitfire_fontdue;\n    pub use spitfire_glow;\n}\n\n#[derive(Debug, Clone, Copy, Pod, Zeroable)]\n#[repr(C)]\npub struct Vertex {\n    pub position: [f32; 2],\n    pub uv: [f32; 3],\n    pub color: [f32; 4],\n}\n\nimpl Default for Vertex {\n    fn default() -> Self {\n        Self {\n            position: Default::default(),\n            uv: Default::default(),\n            color: [1.0, 1.0, 1.0, 1.0],\n        }\n    }\n}\n\nimpl GlowVertexAttribs for Vertex {\n    const ATTRIBS: &'static [(&'static str, GlowVertexAttrib)] = &[\n        (\n            \"a_position\",\n            GlowVertexAttrib::Float {\n                channels: 2,\n                normalized: false,\n            },\n        ),\n        (\n            \"a_uv\",\n            GlowVertexAttrib::Float {\n                channels: 3,\n                normalized: false,\n            },\n        ),\n        (\n            \"a_color\",\n            GlowVertexAttrib::Float {\n                channels: 4,\n                normalized: false,\n            },\n        ),\n    ];\n}\n\nimpl TesselateVertex for Vertex {\n    fn apply(&mut self, position: [f32; 2], tex_coord: [f32; 3], color: [f32; 4]) {\n        self.position = position;\n        self.uv = tex_coord;\n        self.color = color;\n    }\n\n    fn transform(&mut self, matrix: vek::Mat4<f32>) {\n        let result = matrix.mul_point(vek::Vec3 {\n            x: self.position[0],\n            y: self.position[1],\n            z: 0.0,\n        });\n        self.position[0] = result.x;\n        self.position[1] = result.y;\n    }\n}\n\nimpl TextVertex<Color> for Vertex {\n    fn apply(&mut self, position: [f32; 2], tex_coord: [f32; 3], color: Color) {\n        self.position = position;\n        self.uv = tex_coord;\n        self.color = [color.r, color.g, color.b, color.a];\n    }\n}\n\npub(crate) struct TesselateToGraphics<'a> {\n    colored_shader: &'a Shader,\n    textured_shader: &'a Shader,\n    text_shader: &'a Shader,\n    #[cfg(debug_assertions)]\n    debug_shader: Option<&'a Shader>,\n    glyphs_texture: &'a Texture,\n    missing_texture: &'a Texture,\n    assets: &'a AssetsManager,\n    clip_stack: Vec<Rect<i32, i32>>,\n    viewport_height: i32,\n    projection_view_matrix: [f32; 16],\n}\n\nimpl TesselateBatchConverter<GraphicsBatch> for TesselateToGraphics<'_> {\n    fn convert(&mut self, batch: TesselateBatch) -> Option<GraphicsBatch> {\n        match batch {\n            TesselateBatch::Color => Some(GraphicsBatch {\n                shader: Some(self.colored_shader.clone()),\n                blending: GlowBlending::Alpha,\n                scissor: self.clip_stack.last().copied(),\n                ..Default::default()\n            }),\n            TesselateBatch::Image { id } => {\n                let id = AssetsManager::parse_image_id(&id).0;\n                Some(GraphicsBatch {\n                    shader: Some(self.textured_shader.clone()),\n                    textures: vec![(\n                        self.assets\n                            .textures\n                            .get(id)\n                            .map(|texture| texture.texture.clone())\n                            .unwrap_or_else(|| self.missing_texture.clone()),\n                        GlowTextureFiltering::Linear,\n                    )],\n                    blending: GlowBlending::Alpha,\n                    scissor: self.clip_stack.last().copied(),\n                    ..Default::default()\n                })\n            }\n            TesselateBatch::Text => Some(GraphicsBatch {\n                shader: Some(self.text_shader.clone()),\n                textures: vec![(self.glyphs_texture.clone(), GlowTextureFiltering::Linear)],\n                blending: GlowBlending::Alpha,\n                scissor: self.clip_stack.last().copied(),\n                ..Default::default()\n            }),\n            TesselateBatch::Procedural {\n                id,\n                images,\n                parameters,\n            } => Some(GraphicsBatch {\n                shader: self\n                    .assets\n                    .shaders\n                    .get(&id)\n                    .map(|shader| shader.shader.clone()),\n                uniforms: parameters\n                    .into_iter()\n                    .map(|(k, v)| (k.into(), GlowUniformValue::F1(v)))\n                    .chain((0..images.len()).map(|index| {\n                        (\n                            if index > 0 {\n                                format!(\"u_image{index}\").into()\n                            } else {\n                                \"u_image\".into()\n                            },\n                            GlowUniformValue::I1(index as _),\n                        )\n                    }))\n                    .chain(std::iter::once((\n                        \"u_projection_view\".into(),\n                        GlowUniformValue::M4(self.projection_view_matrix),\n                    )))\n                    .collect(),\n                textures: images\n                    .into_iter()\n                    .filter_map(|id| {\n                        Some((\n                            self.assets.textures.get(&id)?.texture.to_owned(),\n                            GlowTextureFiltering::Linear,\n                        ))\n                    })\n                    .collect(),\n                scissor: self.clip_stack.last().copied(),\n                ..Default::default()\n            }),\n            TesselateBatch::ClipPush { x, y, w, h } => {\n                self.clip_stack.push(vek::Rect {\n                    x: x as _,\n                    y: self.viewport_height - y as i32 - h as i32,\n                    w: w as _,\n                    h: h as _,\n                });\n                None\n            }\n            TesselateBatch::ClipPop => {\n                self.clip_stack.pop();\n                None\n            }\n            TesselateBatch::Debug => Some(GraphicsBatch {\n                shader: self.debug_shader.cloned(),\n                wireframe: true,\n                ..Default::default()\n            }),\n        }\n    }\n}\n\npub fn setup(app: &mut Application) {\n    app.register_props::<CanvasProps>(\"CanvasProps\");\n\n    app.register_component(\"canvas\", FnWidget::pointer(canvas));\n}\n"
  },
  {
    "path": "crates/app/src/render_worker.rs",
    "content": "use crate::{AssetsManager, Vertex};\nuse spitfire_glow::{\n    graphics::{Graphics, Shader, Surface},\n    renderer::GlowTextureFormat,\n};\nuse std::collections::HashMap;\n\npub struct RenderWorkerDescriptor {\n    pub id: String,\n    pub width: u32,\n    pub height: u32,\n    pub format: GlowTextureFormat,\n    pub color: [f32; 4],\n}\n\n#[derive(Default)]\npub struct RenderWorkersViewModel {\n    surfaces: HashMap<String, Surface>,\n    commands: Vec<Command>,\n}\n\nimpl RenderWorkersViewModel {\n    pub const VIEW_MODEL: &str = \"RenderWorkersViewModel\";\n\n    pub fn workers(&self) -> impl Iterator<Item = &str> {\n        self.surfaces.keys().map(|s| s.as_str())\n    }\n\n    pub fn add_worker(&mut self, worker: RenderWorkerDescriptor) {\n        self.commands.push(Command::Create { worker });\n    }\n\n    pub fn add_worker_surface(&mut self, id: impl ToString, surface: Surface) {\n        self.commands.push(Command::Add {\n            id: id.to_string(),\n            surface,\n        });\n    }\n\n    pub fn remove_worker(&mut self, worker: &str) {\n        self.commands.push(Command::Remove {\n            worker: worker.to_owned(),\n        });\n    }\n\n    pub fn schedule_task(\n        &mut self,\n        worker: &str,\n        clear: bool,\n        task: impl FnOnce(RenderWorkerTaskContext) + 'static,\n    ) {\n        self.commands.push(Command::Schedule {\n            worker: worker.to_owned(),\n            clear,\n            task: Box::new(task),\n        });\n    }\n\n    pub(crate) fn maintain(\n        &mut self,\n        graphics: &mut Graphics<Vertex>,\n        assets: &mut AssetsManager,\n        colored_shader: &Shader,\n        textured_shader: &Shader,\n        text_shader: &Shader,\n    ) {\n        for command in self.commands.drain(..) {\n            match command {\n                Command::Create { worker } => {\n                    let Ok(texture) =\n                        graphics.texture(worker.width, worker.height, 1, worker.format, None)\n                    else {\n                        continue;\n                    };\n                    let Ok(mut surface) = graphics.surface(vec![texture.clone().into()]) else {\n                        continue;\n                    };\n                    surface.set_color(worker.color);\n                    self.surfaces.insert(worker.id.clone(), surface);\n                    assets.add_texture(worker.id, texture);\n                }\n                Command::Add { id, surface } => {\n                    self.surfaces.insert(id, surface);\n                }\n                Command::Remove { worker } => {\n                    self.surfaces.remove(&worker);\n                    assets.remove_texture(&worker);\n                }\n                Command::Schedule {\n                    worker,\n                    clear,\n                    task,\n                } => {\n                    if let Some(surface) = self.surfaces.get(&worker) {\n                        let _ = graphics.draw();\n                        let _ = graphics.push_surface(surface.clone());\n                        let _ = graphics.prepare_frame(clear);\n                        (task)(RenderWorkerTaskContext {\n                            width: surface.width(),\n                            height: surface.height(),\n                            format: surface.attachments()[0].texture.format(),\n                            graphics,\n                            colored_shader,\n                            textured_shader,\n                            text_shader,\n                        });\n                        let _ = graphics.draw();\n                        let _ = graphics.pop_surface();\n                        let _ = graphics.prepare_frame(false);\n                    }\n                }\n            }\n        }\n    }\n}\n\npub struct RenderWorkerTaskContext<'a> {\n    pub width: u32,\n    pub height: u32,\n    pub format: GlowTextureFormat,\n    pub graphics: &'a mut Graphics<Vertex>,\n    pub colored_shader: &'a Shader,\n    pub textured_shader: &'a Shader,\n    pub text_shader: &'a Shader,\n}\n\nenum Command {\n    Create {\n        worker: RenderWorkerDescriptor,\n    },\n    Add {\n        id: String,\n        surface: Surface,\n    },\n    Remove {\n        worker: String,\n    },\n    Schedule {\n        worker: String,\n        clear: bool,\n        #[allow(clippy::type_complexity)]\n        task: Box<dyn FnOnce(RenderWorkerTaskContext)>,\n    },\n}\n"
  },
  {
    "path": "crates/app/src/text_measurements.rs",
    "content": "use crate::AssetsManager;\nuse fontdue::layout::{\n    CoordinateSystem, HorizontalAlign, Layout, LayoutSettings, TextStyle, VerticalAlign,\n};\nuse raui_core::{\n    layout::{CoordsMapping, default_layout_engine::TextMeasurementEngine},\n    widget::{\n        unit::text::{TextBox, TextBoxHorizontalAlign, TextBoxSizeValue, TextBoxVerticalAlign},\n        utils::{Rect, Vec2},\n    },\n};\nuse raui_tesselate_renderer::*;\nuse spitfire_fontdue::TextRenderer;\n\npub struct AppTextMeasurementsEngine<'a> {\n    pub assets: &'a AssetsManager,\n}\n\nimpl TextMeasurementEngine for AppTextMeasurementsEngine<'_> {\n    fn measure_text(\n        &self,\n        size_available: Vec2,\n        mapping: &CoordsMapping,\n        unit: &TextBox,\n    ) -> Option<Rect> {\n        let font_index = self.assets.font_index_by_id(&unit.font.name)?;\n        let text = TextStyle::with_user_data(\n            &unit.text,\n            unit.font.size * mapping.scalar_scale(false),\n            font_index,\n            unit.color,\n        );\n        let max_width = match unit.width {\n            TextBoxSizeValue::Content => None,\n            TextBoxSizeValue::Fill => Some(size_available.x),\n            TextBoxSizeValue::Exact(v) => Some(v),\n        };\n        let max_height = match unit.height {\n            TextBoxSizeValue::Content => None,\n            TextBoxSizeValue::Fill => Some(size_available.y),\n            TextBoxSizeValue::Exact(v) => Some(v),\n        };\n        let mut layout = Layout::new(CoordinateSystem::PositiveYDown);\n        layout.reset(&LayoutSettings {\n            max_width,\n            max_height,\n            horizontal_align: match unit.horizontal_align {\n                TextBoxHorizontalAlign::Left => HorizontalAlign::Left,\n                TextBoxHorizontalAlign::Center => HorizontalAlign::Center,\n                TextBoxHorizontalAlign::Right => HorizontalAlign::Right,\n            },\n            vertical_align: match unit.vertical_align {\n                TextBoxVerticalAlign::Top => VerticalAlign::Top,\n                TextBoxVerticalAlign::Middle => VerticalAlign::Middle,\n                TextBoxVerticalAlign::Bottom => VerticalAlign::Bottom,\n            },\n            ..Default::default()\n        });\n        layout.append(self.assets.fonts(), &text);\n        let aabb = TextRenderer::measure(&layout, self.assets.fonts(), false);\n        if aabb.iter().all(|v| v.is_finite()) {\n            Some(Rect {\n                left: aabb[0],\n                top: aabb[1],\n                right: aabb[2],\n                bottom: aabb[3],\n            })\n        } else {\n            None\n        }\n    }\n}\n"
  },
  {
    "path": "crates/core/Cargo.toml",
    "content": "[package]\nname = \"raui-core\"\nversion = \"0.70.17\"\nauthors = [\"Patryk 'PsichiX' Budzynski <psichix@gmail.com>\"]\nedition = \"2024\"\ndescription = \"RAUI application layer\"\nreadme = \"../../README.md\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/RAUI-labs/raui\"\nkeywords = [\"renderer\", \"agnostic\", \"ui\", \"interface\", \"gamedev\"]\ncategories = [\"gui\", \"rendering::graphics-api\"]\n\n[dependencies]\nraui-derive = { version = \"0.70\", path = \"../derive\" }\nserde = { version = \"1\", features = [\"derive\", \"rc\"] }\nserde_json = \"1\"\nintuicio-data = \"0.51\"\n"
  },
  {
    "path": "crates/core/src/animator.rs",
    "content": "//! Animation engine\n//!\n//! RAUI widget components can be animated by updating and adding animations using the [`Animator`]\n//! inside of widget lifecycle hooks and by reading the progress of those animations from the\n//! [`AnimatorStates`] provided by the [`WidgetContext`].\n//!\n//! See [`Animator`] and [`AnimatorStates`] for code samples.\n//!\n//! [`WidgetContext`]: crate::widget::context::WidgetContext\nuse crate::{MessageData, Scalar, messenger::MessageSender, widget::WidgetId};\nuse serde::{Deserialize, Serialize};\nuse std::{collections::HashMap, sync::mpsc::Sender};\n\n/// An error that may occur when animating a value\npub enum AnimationError {\n    /// Could not read animation data\n    CouldNotReadData,\n    /// Could not write animation data\n    CouldNotWriteData,\n}\n\n/// Handle to an animation sending channel used internally to update widget animations values in\n/// lifecycle hooks\n#[derive(Clone)]\npub(crate) struct AnimationUpdate(Sender<(String, Option<Animation>)>);\n\nimpl AnimationUpdate {\n    pub fn new(sender: Sender<(String, Option<Animation>)>) -> Self {\n        Self(sender)\n    }\n\n    pub fn change(&self, name: &str, data: Option<Animation>) -> Result<(), AnimationError> {\n        if self.0.send((name.to_owned(), data)).is_err() {\n            Err(AnimationError::CouldNotWriteData)\n        } else {\n            Ok(())\n        }\n    }\n}\n\n/// Allows manipulating widget animations\n///\n/// An [`Animator`] can be used inside of the [`WidgetMountOrChangeContext`] that is provided when\n/// setting widget lifecycle handlers.\n///\n/// # Animations & Values\n///\n/// The animator can manage any number of different animations identified by a string `anim_id`.\n/// Additionally each animation can have more than one _value_ that is animated and each of these\n/// values has a `value_name` that can be used to get the animated value.\n///\n/// [`WidgetMountOrChangeContext`]: crate::widget::context::WidgetMountOrChangeContext\npub struct Animator<'a> {\n    states: &'a AnimatorStates,\n    update: AnimationUpdate,\n}\n\nimpl<'a> Animator<'a> {\n    /// Create a new [`Animator`]\n    #[inline]\n    pub(crate) fn new(states: &'a AnimatorStates, update: AnimationUpdate) -> Self {\n        Self { states, update }\n    }\n\n    /// Check whether or not the widget has an animation with the given `anim_id`\n    #[inline]\n    pub fn has(&self, anim_id: &str) -> bool {\n        self.states.has(anim_id)\n    }\n\n    /// Change the animation associated to a given `anim_id`\n    #[inline]\n    pub fn change(\n        &self,\n        anim_id: &str,\n        animation: Option<Animation>,\n    ) -> Result<(), AnimationError> {\n        self.update.change(anim_id, animation)\n    }\n\n    /// Get the current progress of the animation of a given value\n    ///\n    /// This will return [`None`] if the value is not currently being animated.\n    #[inline]\n    pub fn value_progress(&self, anim_id: &str, value_name: &str) -> Option<AnimatedValueProgress> {\n        self.states.value_progress(anim_id, value_name)\n    }\n\n    /// Get the current progress factor of the animation of a given value\n    ///\n    /// If the value is currently being animated this will return [`Some`] [`Scalar`] between `0`\n    /// and `1` with `0` meaning just started and `1` meaning finished.\n    ///\n    /// If the value is **not** currently being animated [`None`] will be returned\n    #[inline]\n    pub fn value_progress_factor(&self, anim_id: &str, value_name: &str) -> Option<Scalar> {\n        self.states\n            .value_progress(anim_id, value_name)\n            .map(|x| x.progress_factor)\n    }\n\n    /// Same as [`value_progress_factor`][Self::value_progress_factor] but returning `default` instead of [`None`]\n    #[inline]\n    pub fn value_progress_factor_or(\n        &self,\n        anim_id: &str,\n        value_name: &str,\n        default: Scalar,\n    ) -> Scalar {\n        self.value_progress_factor(anim_id, value_name)\n            .unwrap_or(default)\n    }\n\n    /// Same as [`value_progress_factor`][Self::value_progress_factor] but returning `0` instead of [`None`]\n    #[inline]\n    pub fn value_progress_factor_or_zero(&self, anim_id: &str, value_name: &str) -> Scalar {\n        self.value_progress_factor(anim_id, value_name)\n            .unwrap_or(0.)\n    }\n}\n\n/// The amount of progress made for a value in an animation\n#[derive(Debug, Default, Clone, Copy)]\npub struct AnimatedValueProgress {\n    /// How far along this animation is from 0 to 1\n    pub progress_factor: Scalar,\n    /// The amount of time this animation has been running\n    pub time: Scalar,\n    /// The amount of time that this animation will run for\n    pub duration: Scalar,\n}\n\n/// The current state of animations in a component\n///\n/// The [`AnimatorStates`] can be accessed from the [`WidgetContext`] to get information about the\n/// current state of all component animations.\n///\n/// # Animations & Values\n///\n/// A component may have any number of different animations identified by a string `anim_id`.\n/// Additionally each animation can have more than one _value_ that is animated and each of these\n/// values has a `value_name` that can be used to get the animated value.\n///\n/// [`WidgetContext`]: crate::widget::context::WidgetContext\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct AnimatorStates(\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"HashMap::is_empty\")]\n    pub HashMap<String, AnimatorState>,\n);\n\nimpl AnimatorStates {\n    /// Initialize a new [`AnimatorStates`] that contains a single animation\n    pub(crate) fn new(anim_id: String, animation: Animation) -> Self {\n        let mut result = HashMap::with_capacity(1);\n        result.insert(anim_id, AnimatorState::new(animation));\n        Self(result)\n    }\n\n    /// Returns whether or not _any_ of the animations for this component are in-progress\n    pub fn in_progress(&self) -> bool {\n        self.0.values().any(|s| s.in_progress())\n    }\n\n    /// Returns `true` if none of this component's animations are currently running\n    #[inline]\n    pub fn is_done(&self) -> bool {\n        !self.in_progress()\n    }\n\n    /// Returns true if the widget has an animation with the given `anim_id`\n    #[inline]\n    pub fn has(&self, anim_id: &str) -> bool {\n        self.0.contains_key(anim_id)\n    }\n\n    /// Get the current progress of the animation of a given value\n    ///\n    /// This will return [`None`] if the value is not currently being animated.\n    #[inline]\n    pub fn value_progress(&self, anim_id: &str, value_name: &str) -> Option<AnimatedValueProgress> {\n        if let Some(state) = self.0.get(anim_id) {\n            state.value_progress(value_name)\n        } else {\n            None\n        }\n    }\n\n    /// Get the current progress factor of the animation of a given value\n    ///\n    /// If the value is currently being animated this will return [`Some`] [`Scalar`] between `0`\n    /// and `1` with `0` meaning just started and `1` meaning finished.\n    ///\n    /// If the value is **not** currently being animated [`None`] will be returned\n    #[inline]\n    pub fn value_progress_factor(&self, anim_id: &str, value_name: &str) -> Option<Scalar> {\n        if let Some(state) = self.0.get(anim_id) {\n            state.value_progress_factor(value_name)\n        } else {\n            None\n        }\n    }\n\n    /// Same as [`value_progress_factor`][Self::value_progress_factor] but returning `default` instead of [`None`]\n    #[inline]\n    pub fn value_progress_factor_or(\n        &self,\n        anim_id: &str,\n        value_name: &str,\n        default: Scalar,\n    ) -> Scalar {\n        self.value_progress_factor(anim_id, value_name)\n            .unwrap_or(default)\n    }\n\n    /// Same as [`value_progress_factor`][Self::value_progress_factor] but returning `0` instead of [`None`]\n    #[inline]\n    pub fn value_progress_factor_or_zero(&self, anim_id: &str, value_name: &str) -> Scalar {\n        self.value_progress_factor(anim_id, value_name)\n            .unwrap_or(0.)\n    }\n\n    /// Update the animation with the given `anim_id`\n    ///\n    /// If `animation` is [`None`] the animation will be removed.\n    pub fn change(&mut self, anim_id: String, animation: Option<Animation>) {\n        if let Some(animation) = animation {\n            self.0.insert(anim_id, AnimatorState::new(animation));\n        } else {\n            self.0.remove(&anim_id);\n        }\n    }\n\n    /// Processes the animations, updating the values of each animation baed on the progressed time\n    pub(crate) fn process(\n        &mut self,\n        delta_time: Scalar,\n        owner: &WidgetId,\n        message_sender: &MessageSender,\n    ) {\n        for state in self.0.values_mut() {\n            state.process(delta_time, owner, message_sender);\n        }\n    }\n}\n\n/// The state of a single animation in a component\n///\n/// This is most often accessed though [`AnimatorStates`] in the [`WidgetContext`].\n///\n/// [`WidgetContext`]: crate::widget::context::WidgetContext\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct AnimatorState {\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"HashMap::is_empty\")]\n    sheet: HashMap<String, AnimationPhase>,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"Vec::is_empty\")]\n    messages: Vec<(Scalar, String)>,\n    #[serde(default)]\n    time: Scalar,\n    #[serde(default)]\n    duration: Scalar,\n    #[serde(default)]\n    looped: bool,\n}\n\nimpl AnimatorState {\n    /// Initialize a new [`AnimatorState`] given an animation\n    pub(crate) fn new(animation: Animation) -> Self {\n        let mut sheet = HashMap::new();\n        let mut messages = vec![];\n        let (time, looped) = Self::include_animation(animation, &mut sheet, &mut messages, 0.0);\n        Self {\n            sheet,\n            messages,\n            time: 0.0,\n            duration: time,\n            looped,\n        }\n    }\n\n    /// Returns whether or not the animations is in-progress\n    #[inline]\n    pub fn in_progress(&self) -> bool {\n        self.looped || (self.time <= self.duration && !self.sheet.is_empty())\n    }\n\n    /// Returns `true` if this animation is not in-progress\n    #[inline]\n    pub fn is_done(&self) -> bool {\n        !self.in_progress()\n    }\n\n    /// Get the current progress of the animation of a given value\n    ///\n    /// This will return [`None`] if the value is not currently being animated.\n    #[inline]\n    pub fn value_progress(&self, name: &str) -> Option<AnimatedValueProgress> {\n        self.sheet.get(name).map(|p| AnimatedValueProgress {\n            progress_factor: p.cached_progress,\n            time: p.cached_time,\n            duration: p.duration,\n        })\n    }\n\n    /// Get the current progress factor of the animation of a given value\n    ///\n    /// If the value is currently being animated this will return [`Some`] [`Scalar`] between `0`\n    /// and `1` with `0` meaning just started and `1` meaning finished.\n    ///\n    /// If the value is **not** currently being animated [`None`] will be returned\n    #[inline]\n    pub fn value_progress_factor(&self, name: &str) -> Option<Scalar> {\n        self.sheet.get(name).map(|p| p.cached_progress)\n    }\n\n    /// Same as [`value_progress_factor`][Self::value_progress_factor] but returning `default` instead of [`None`]\n    #[inline]\n    pub fn value_progress_factor_or(&self, name: &str, default: Scalar) -> Scalar {\n        self.value_progress_factor(name).unwrap_or(default)\n    }\n\n    /// Same as [`value_progress_factor`][Self::value_progress_factor] but returning `0` instead of [`None`]\n    #[inline]\n    pub fn value_progress_factor_or_zero(&self, name: &str) -> Scalar {\n        self.value_progress_factor(name).unwrap_or(0.)\n    }\n\n    /// Processes the animations, updating the values of each animation baed on the progressed time\n    pub(crate) fn process(\n        &mut self,\n        delta_time: Scalar,\n        owner: &WidgetId,\n        message_sender: &MessageSender,\n    ) {\n        if delta_time > 0.0 {\n            if self.looped && self.time > self.duration {\n                self.time = 0.0;\n            }\n            let old_time = self.time;\n            self.time += delta_time;\n            for phase in self.sheet.values_mut() {\n                phase.cached_time = (self.time - phase.start).min(phase.duration).max(0.0);\n                phase.cached_progress = if phase.duration > 0.0 {\n                    phase.cached_time / phase.duration\n                } else {\n                    0.0\n                };\n            }\n            for (time, message) in &self.messages {\n                if *time >= old_time && *time < self.time {\n                    message_sender.write(owner.to_owned(), AnimationMessage(message.to_owned()));\n                }\n            }\n        }\n    }\n\n    // Add an animation to this [`AnimatorState`] recursively\n    fn include_animation(\n        animation: Animation,\n        sheet: &mut HashMap<String, AnimationPhase>,\n        messages: &mut Vec<(Scalar, String)>,\n        mut time: Scalar,\n    ) -> (Scalar, bool) {\n        match animation {\n            Animation::Value(value) => {\n                let duration = value.duration.max(0.0);\n                let phase = AnimationPhase {\n                    start: time,\n                    duration,\n                    cached_time: 0.0,\n                    cached_progress: 0.0,\n                };\n                sheet.insert(value.name, phase);\n                (time + duration, false)\n            }\n            Animation::Sequence(anims) => {\n                for anim in anims {\n                    time = Self::include_animation(anim, sheet, messages, time).0;\n                }\n                (time, false)\n            }\n            Animation::Parallel(anims) => {\n                let mut result = time;\n                for anim in anims {\n                    result = Self::include_animation(anim, sheet, messages, time)\n                        .0\n                        .max(result);\n                }\n                (result, false)\n            }\n            Animation::Looped(anim) => {\n                let looped = sheet.is_empty();\n                time = Self::include_animation(*anim, sheet, messages, time).0;\n                (time, looped)\n            }\n            Animation::TimeShift(v) => ((time - v).max(0.0), false),\n            Animation::Message(message) => {\n                messages.push((time, message));\n                (time, false)\n            }\n        }\n    }\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\nstruct AnimationPhase {\n    #[serde(default)]\n    pub start: Scalar,\n    #[serde(default)]\n    pub duration: Scalar,\n    #[serde(default)]\n    pub cached_time: Scalar,\n    #[serde(default)]\n    pub cached_progress: Scalar,\n}\n\n/// Defines a widget animation\n///\n/// [`Animation`]'s can be added to widget component's [`AnimatorStates`] to animate values.\n///\n/// Creating an [`Animation`] doesn't actually animate a specific value, but instead gives you a way\n/// to track the _progress_ of an animated value using the\n/// [`value_progress`][AnimatorStates::value_progress] function. This allows you to use the progress\n/// to calculate how to interpolate the real values when you build your widget.\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub enum Animation {\n    /// A single animated value with a name and a duration\n    Value(AnimatedValue),\n    /// A sequence of animations that will be run in a row\n    Sequence(Vec<Animation>),\n    /// A set of animations that will be run at the same time\n    Parallel(Vec<Animation>),\n    /// An animation that will play in a loop\n    Looped(Box<Animation>),\n    /// TODO: Document `TimeShift`\n    TimeShift(Scalar),\n    /// Send an [`AnimationMessage`]\n    Message(String),\n}\n\nimpl Default for Animation {\n    fn default() -> Self {\n        Self::TimeShift(0.0)\n    }\n}\n\n/// A single, animated value with a name and a duration\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct AnimatedValue {\n    /// The name of the animated value\n    ///\n    /// This is used to get the progress of the animation value with the\n    /// [`value_progress`][AnimatorStates::value_progress] function.\n    #[serde(default)]\n    pub name: String,\n    /// The duration of the animation\n    #[serde(default)]\n    pub duration: Scalar,\n}\n\n/// A [`MessageData`][crate::messenger::MessageData] implementation sent by running an\n/// [`Animation::Message`] animation\n#[derive(MessageData, Debug, Default, Clone)]\n#[message_data(crate::messenger::MessageData)]\npub struct AnimationMessage(pub String);\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use std::{str::FromStr, sync::mpsc::channel};\n\n    #[test]\n    fn test_animator() {\n        let animation = Animation::Sequence(vec![\n            Animation::Value(AnimatedValue {\n                name: \"fade-in\".to_owned(),\n                duration: 0.2,\n            }),\n            Animation::Value(AnimatedValue {\n                name: \"delay\".to_owned(),\n                duration: 0.6,\n            }),\n            Animation::Value(AnimatedValue {\n                name: \"fade-out\".to_owned(),\n                duration: 0.2,\n            }),\n            Animation::Message(\"next\".to_owned()),\n        ]);\n        println!(\"Animation: {animation:#?}\");\n        let mut states = AnimatorStates::new(\"\".to_owned(), animation);\n        println!(\"States 0: {states:#?}\");\n        let id = WidgetId::from_str(\"type:/widget\").unwrap();\n        let (sender, receiver) = channel();\n        let sender = MessageSender::new(sender);\n        states.process(0.5, &id, &sender);\n        println!(\"States 1: {states:#?}\");\n        states.process(0.6, &id, &sender);\n        println!(\"States 2: {states:#?}\");\n        println!(\n            \"Message: {:#?}\",\n            receiver\n                .try_recv()\n                .unwrap()\n                .1\n                .as_any()\n                .downcast_ref::<AnimationMessage>()\n                .unwrap()\n        );\n    }\n}\n"
  },
  {
    "path": "crates/core/src/application.rs",
    "content": "//! Application foundation used to drive the RAUI interface\n//!\n//! An [`Application`] is the struct that pulls together all the pieces of a RAUI ui such as layout,\n//! interaction, animations, etc.\n//!\n//! In most cases users will not need to manually create and manage an [`Application`]. That will\n//! usually be handled by renderer integration crates like [`raui-tesselation-renderer`].\n//!\n//! [`raui-tesselation-renderer`]: https://docs.rs/raui-tesselation-renderer/\n//!\n//! You _will_ need to interact with [`Application`] if you are building your own RAUI integration\n//! with another renderer or game engine.\n//! ```\n\nuse crate::{\n    Prefab, PrefabError, PrefabValue, Scalar,\n    animator::{AnimationUpdate, Animator, AnimatorStates},\n    interactive::InteractionsEngine,\n    layout::{CoordsMapping, Layout, LayoutEngine},\n    messenger::{Message, MessageData, MessageSender, Messages, Messenger},\n    props::{Props, PropsData, PropsRegistry},\n    renderer::Renderer,\n    signals::{Signal, SignalSender},\n    state::{State, StateChange, StateUpdate},\n    view_model::{ViewModel, ViewModelCollection, ViewModelCollectionView},\n    widget::{\n        FnWidget, WidgetId, WidgetIdCommon, WidgetLifeCycle,\n        component::{\n            WidgetComponent, WidgetComponentPrefab, containers::responsive_box::MediaQueryViewModel,\n        },\n        context::{WidgetContext, WidgetMountOrChangeContext, WidgetUnmountContext},\n        node::{WidgetNode, WidgetNodePrefab},\n        unit::{\n            WidgetUnit, WidgetUnitNode, WidgetUnitNodePrefab,\n            area::{AreaBoxNode, AreaBoxNodePrefab},\n            content::{\n                ContentBoxItem, ContentBoxItemNode, ContentBoxItemNodePrefab, ContentBoxNode,\n                ContentBoxNodePrefab,\n            },\n            flex::{\n                FlexBoxItem, FlexBoxItemNode, FlexBoxItemNodePrefab, FlexBoxNode, FlexBoxNodePrefab,\n            },\n            grid::{\n                GridBoxItem, GridBoxItemNode, GridBoxItemNodePrefab, GridBoxNode, GridBoxNodePrefab,\n            },\n            image::{ImageBoxNode, ImageBoxNodePrefab},\n            portal::{\n                PortalBox, PortalBoxNode, PortalBoxNodePrefab, PortalBoxSlot, PortalBoxSlotNode,\n                PortalBoxSlotNodePrefab,\n            },\n            size::{SizeBoxNode, SizeBoxNodePrefab},\n            text::{TextBoxNode, TextBoxNodePrefab},\n        },\n    },\n};\nuse std::{\n    borrow::Cow,\n    collections::{HashMap, HashSet},\n    convert::TryInto,\n    sync::{\n        Arc, RwLock,\n        mpsc::{Sender, channel},\n    },\n};\n\n/// Errors that can occur while interacting with an application\n#[derive(Debug, Clone)]\npub enum ApplicationError {\n    Prefab(PrefabError),\n    ComponentMappingNotFound(String),\n}\n\nimpl From<PrefabError> for ApplicationError {\n    fn from(error: PrefabError) -> Self {\n        Self::Prefab(error)\n    }\n}\n\n/// Indicates the reason that an [`Application`] state was invalidated and had to be re-rendered\n///\n/// You can get the last invalidation cause of an application using [`last_invalidation_cause`]\n///\n/// [`last_invalidation_cause`]: Application::last_invalidation_cause\n#[derive(Debug, Default, Clone)]\npub enum InvalidationCause {\n    /// Application not invalidated\n    #[default]\n    None,\n    /// Application update caused by change in widgets common root.\n    CommonRootUpdate(WidgetIdCommon),\n}\n\n#[derive(Clone)]\npub struct ChangeNotifier(Arc<RwLock<HashSet<WidgetId>>>);\n\nimpl ChangeNotifier {\n    pub fn notify(&self, id: WidgetId) {\n        if let Ok(mut ids) = self.0.write() {\n            ids.insert(id);\n        }\n    }\n}\n\n/// Contains and orchestrates application layout, animations, interactions, etc.\n///\n/// See the [`application`][self] module for more information and examples.\npub struct Application {\n    component_mappings: HashMap<String, FnWidget>,\n    props_registry: PropsRegistry,\n    tree: WidgetNode,\n    rendered_tree: WidgetUnit,\n    layout: Layout,\n    states: HashMap<WidgetId, Props>,\n    state_changes: HashMap<WidgetId, Vec<StateChange>>,\n    animators: HashMap<WidgetId, AnimatorStates>,\n    messages: HashMap<WidgetId, Messages>,\n    pending_stack: Vec<WidgetStackItem>,\n    done_stack: Vec<WidgetNode>,\n    signals: Vec<Signal>,\n    pub view_models: ViewModelCollection,\n    changes: ChangeNotifier,\n    #[allow(clippy::type_complexity)]\n    unmount_closures: HashMap<WidgetId, Vec<Box<dyn FnMut(WidgetUnmountContext) + Send + Sync>>>,\n    dirty: WidgetIdCommon,\n    render_changed: bool,\n    last_invalidation_cause: InvalidationCause,\n    /// The amount of time between the last update, used when calculating animation progress\n    pub animations_delta_time: Scalar,\n}\n\nimpl Default for Application {\n    fn default() -> Self {\n        let mut view_models = ViewModelCollection::default();\n        view_models.insert(\n            MediaQueryViewModel::VIEW_MODEL.to_string(),\n            ViewModel::produce(MediaQueryViewModel::new),\n        );\n        Self {\n            component_mappings: Default::default(),\n            props_registry: Default::default(),\n            tree: Default::default(),\n            rendered_tree: Default::default(),\n            layout: Default::default(),\n            states: Default::default(),\n            state_changes: Default::default(),\n            animators: Default::default(),\n            messages: Default::default(),\n            pending_stack: Default::default(),\n            done_stack: Default::default(),\n            signals: Default::default(),\n            view_models,\n            changes: ChangeNotifier(Default::default()),\n            unmount_closures: Default::default(),\n            dirty: Default::default(),\n            render_changed: false,\n            last_invalidation_cause: Default::default(),\n            animations_delta_time: 0.0,\n        }\n    }\n}\n\nimpl Application {\n    /// Setup the application with a given a setup function\n    ///\n    /// We need to run the `setup` function for the application to register components and\n    /// properties if we want to support serialization of the UI. We pass it a function that will do\n    /// the actual registration.\n    ///\n    /// > **Note:** RAUI will work fine without running any `setup` if UI serialization is not\n    /// > required.\n    #[inline]\n    pub fn setup<F>(&mut self, mut f: F)\n    where\n        F: FnMut(&mut Self),\n    {\n        (f)(self);\n    }\n\n    pub fn notifier(&self) -> ChangeNotifier {\n        self.changes.clone()\n    }\n\n    /// Register's a component under a string name used when serializing the UI\n    ///\n    /// This function is often used in [`setup`][Self::setup] functions for registering batches of\n    /// components.\n    #[inline]\n    pub fn register_component(&mut self, type_name: &str, processor: FnWidget) {\n        self.component_mappings\n            .insert(type_name.to_owned(), processor);\n    }\n\n    /// Unregisters a component\n    ///\n    /// See [`register_component`][Self::register_component]\n    #[inline]\n    pub fn unregister_component(&mut self, type_name: &str) {\n        self.component_mappings.remove(type_name);\n    }\n\n    /// Register's a property type under a string name used when serializing the UI\n    ///\n    /// This function is often used in [`setup`][Self::setup] functions for registering batches of\n    /// properties.\n    #[inline]\n    pub fn register_props<T>(&mut self, name: &str)\n    where\n        T: 'static + Prefab + PropsData,\n    {\n        self.props_registry.register_factory::<T>(name);\n    }\n\n    /// Unregisters a property type\n    ///\n    /// See [`register_props`][Self::register_props]\n    #[inline]\n    pub fn unregister_props(&mut self, name: &str) {\n        self.props_registry.unregister_factory(name);\n    }\n\n    /// Serialize the given [`Props`] to a [`PrefabValue`]\n    #[inline]\n    pub fn serialize_props(&self, props: &Props) -> Result<PrefabValue, PrefabError> {\n        self.props_registry.serialize(props)\n    }\n\n    /// Deserialize [`Props`] from a [`PrefabValue`]\n    #[inline]\n    pub fn deserialize_props(&self, data: PrefabValue) -> Result<Props, PrefabError> {\n        self.props_registry.deserialize(data)\n    }\n\n    /// Serialize a [`WidgetNode`] to a [`PrefabValue`]\n    #[inline]\n    pub fn serialize_node(&self, data: &WidgetNode) -> Result<PrefabValue, ApplicationError> {\n        Ok(self.node_to_prefab(data)?.to_prefab()?)\n    }\n\n    /// Deserialize a [`WidgetNode`] from a [`PrefabValue`]\n    #[inline]\n    pub fn deserialize_node(&self, data: PrefabValue) -> Result<WidgetNode, ApplicationError> {\n        self.node_from_prefab(WidgetNodePrefab::from_prefab(data)?)\n    }\n\n    /// Get the reason that the application state was last invalidated and caused to re-process\n    #[inline]\n    pub fn last_invalidation_cause(&self) -> &InvalidationCause {\n        &self.last_invalidation_cause\n    }\n\n    /// Return's common root widget ID of widgets that has to be to be re-processed\n    #[inline]\n    pub fn dirty(&self) -> &WidgetIdCommon {\n        &self.dirty\n    }\n\n    /// Force mark the application as needing to re-process its root\n    #[inline]\n    pub fn mark_dirty(&mut self) {\n        self.dirty = WidgetIdCommon::new(WidgetId::empty());\n    }\n\n    #[inline]\n    pub fn does_render_changed(&self) -> bool {\n        self.render_changed\n    }\n\n    /// Get the [`WidgetNode`] for the application tree\n    #[inline]\n    pub fn tree(&self) -> &WidgetNode {\n        &self.tree\n    }\n\n    /// Get the application widget tree rendered to raw [`WidgetUnit`]'s\n    #[inline]\n    pub fn rendered_tree(&self) -> &WidgetUnit {\n        &self.rendered_tree\n    }\n\n    /// Get the application [`Layout`] data\n    #[inline]\n    pub fn layout_data(&self) -> &Layout {\n        &self.layout\n    }\n\n    #[inline]\n    pub fn has_layout_widget(&self, id: &WidgetId) -> bool {\n        self.layout.items.keys().any(|k| k == id)\n    }\n\n    /// Update the application widget tree\n    #[inline]\n    pub fn apply(&mut self, tree: impl Into<WidgetNode>) {\n        self.mark_dirty();\n        self.tree = tree.into();\n    }\n\n    /// Render the application\n    #[inline]\n    pub fn render<R, T, E>(&self, mapping: &CoordsMapping, renderer: &mut R) -> Result<T, E>\n    where\n        R: Renderer<T, E>,\n    {\n        renderer.render(&self.rendered_tree, mapping, &self.layout)\n    }\n\n    /// Render the application, but only if something effecting the rendering has changed and it\n    /// _needs_ to be re-rendered\n    #[inline]\n    pub fn render_change<R, T, E>(\n        &mut self,\n        mapping: &CoordsMapping,\n        renderer: &mut R,\n    ) -> Result<Option<T>, E>\n    where\n        R: Renderer<T, E>,\n    {\n        if self.render_changed {\n            Ok(Some(self.render(mapping, renderer)?))\n        } else {\n            Ok(None)\n        }\n    }\n\n    /// Calculate application layout\n    #[inline]\n    pub fn layout<L, E>(&mut self, mapping: &CoordsMapping, layout_engine: &mut L) -> Result<(), E>\n    where\n        L: LayoutEngine<E>,\n    {\n        self.layout = layout_engine.layout(mapping, &self.rendered_tree)?;\n        if let Some(view_model) = self.view_models.get_mut(MediaQueryViewModel::VIEW_MODEL)\n            && let Some(mut view_model) = view_model.write::<MediaQueryViewModel>()\n        {\n            view_model\n                .screen_size\n                .set_unique_notify(self.layout.ui_space.size());\n        }\n        Ok(())\n    }\n\n    /// Calculate application layout, but only if something effecting application layout has changed\n    /// and the layout _needs_ to be re-done\n    #[inline]\n    pub fn layout_change<L, E>(\n        &mut self,\n        mapping: &CoordsMapping,\n        layout_engine: &mut L,\n    ) -> Result<bool, E>\n    where\n        L: LayoutEngine<E>,\n    {\n        if self.render_changed {\n            self.layout(mapping, layout_engine)?;\n            Ok(true)\n        } else {\n            Ok(false)\n        }\n    }\n\n    /// Perform interactions on the application using the given interaction engine\n    #[inline]\n    pub fn interact<I, R, E>(&mut self, interactions_engine: &mut I) -> Result<R, E>\n    where\n        I: InteractionsEngine<R, E>,\n    {\n        interactions_engine.perform_interactions(self)\n    }\n\n    /// Send a message to the given widget\n    #[inline]\n    pub fn send_message<T>(&mut self, id: &WidgetId, data: T)\n    where\n        T: 'static + MessageData,\n    {\n        self.send_message_raw(id, Box::new(data));\n    }\n\n    /// Send raw message data to the given widget\n    #[inline]\n    pub fn send_message_raw(&mut self, id: &WidgetId, data: Message) {\n        if let Some(list) = self.messages.get_mut(id) {\n            list.push(data);\n        } else {\n            self.messages.insert(id.to_owned(), vec![data]);\n        }\n    }\n\n    /// Get the list of [signals][crate::signals] that have been sent by widgets\n    #[inline]\n    pub fn signals(&self) -> &[Signal] {\n        &self.signals\n    }\n\n    /// Get the list of [signals][crate::signals] that have been sent by widgets, consuming the\n    /// current list so that further calls will not include previously sent signals\n    #[inline]\n    pub fn consume_signals(&mut self) -> Vec<Signal> {\n        std::mem::take(&mut self.signals)\n    }\n\n    /// [`process()`][Self::process] application, even if no changes have been detected\n    #[inline]\n    pub fn forced_process(&mut self) -> bool {\n        self.mark_dirty();\n        self.process()\n    }\n\n    /// [Process][Self::process] the application.\n    pub fn process(&mut self) -> bool {\n        self.dirty\n            .include_other(&self.view_models.consume_notified_common_root());\n        if let Ok(mut ids) = self.changes.0.write() {\n            for id in ids.drain() {\n                self.dirty.include(&id);\n            }\n        }\n        self.animations_delta_time = self.animations_delta_time.max(0.0);\n        self.last_invalidation_cause = InvalidationCause::None;\n        self.render_changed = false;\n        let changed_states = std::mem::take(&mut self.state_changes);\n        for id in changed_states.keys() {\n            self.dirty.include(id);\n        }\n        let mut messages = std::mem::take(&mut self.messages);\n        for id in messages.keys() {\n            self.dirty.include(id);\n        }\n        for (id, animator) in &self.animators {\n            if animator.in_progress() {\n                self.dirty.include(id);\n            }\n        }\n        if !self.dirty.is_valid() {\n            return false;\n        }\n        self.last_invalidation_cause = InvalidationCause::CommonRootUpdate(self.dirty.to_owned());\n        let (message_sender, message_receiver) = channel();\n        let message_sender = MessageSender::new(message_sender);\n        for (k, a) in &mut self.animators {\n            a.process(self.animations_delta_time, k, &message_sender);\n        }\n        let mut states = std::mem::take(&mut self.states);\n        for (id, changes) in changed_states {\n            let state = states.entry(id).or_default();\n            for change in changes {\n                match change {\n                    StateChange::Set(props) => {\n                        *state = props;\n                    }\n                    StateChange::Include(props) => {\n                        state.merge_from(props);\n                    }\n                    StateChange::Exclude(type_id) => unsafe {\n                        state.remove_by_type(type_id);\n                    },\n                }\n            }\n        }\n        let (signal_sender, signal_receiver) = channel();\n        let tree = self.tree.clone();\n        let mut used_ids = HashSet::new();\n        let mut new_states = HashMap::new();\n        let rendered_tree = self.process_nodes_stack(\n            tree,\n            &states,\n            &mut messages,\n            &mut new_states,\n            &mut used_ids,\n            &message_sender,\n            &signal_sender,\n        );\n        self.states = states\n            .into_iter()\n            .chain(new_states)\n            .filter(|(id, state)| {\n                if used_ids.contains(id) {\n                    true\n                } else {\n                    if let Some(closures) = self.unmount_closures.remove(id) {\n                        for mut closure in closures {\n                            let messenger = &message_sender;\n                            let signals = SignalSender::new(id.clone(), signal_sender.clone());\n                            let view_models =\n                                ViewModelCollectionView::new(id, &mut self.view_models);\n                            let context = WidgetUnmountContext {\n                                id,\n                                state,\n                                messenger,\n                                signals,\n                                view_models,\n                            };\n                            (closure)(context);\n                        }\n                    }\n                    self.animators.remove(id);\n                    self.view_models.unbind_all(id);\n                    self.view_models.remove_widget_view_models(id);\n                    false\n                }\n            })\n            .collect();\n        while let Ok((id, message)) = message_receiver.try_recv() {\n            if let Some(list) = self.messages.get_mut(&id) {\n                list.push(message);\n            } else {\n                self.messages.insert(id, vec![message]);\n            }\n        }\n        self.signals.clear();\n        while let Ok(data) = signal_receiver.try_recv() {\n            self.signals.push(data);\n        }\n        self.animators = std::mem::take(&mut self.animators)\n            .into_iter()\n            .filter_map(|(k, a)| if a.in_progress() { Some((k, a)) } else { None })\n            .collect();\n        self.dirty = Default::default();\n        if let Ok(tree) = rendered_tree.try_into() {\n            self.rendered_tree = Self::teleport_portals(tree);\n            true\n        } else {\n            false\n        }\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    fn process_nodes_stack(\n        &mut self,\n        root_node: WidgetNode,\n        states: &HashMap<WidgetId, Props>,\n        messages: &mut HashMap<WidgetId, Messages>,\n        new_states: &mut HashMap<WidgetId, Props>,\n        used_ids: &mut HashSet<WidgetId>,\n        message_sender: &MessageSender,\n        signal_sender: &Sender<Signal>,\n    ) -> WidgetNode {\n        self.pending_stack.clear();\n        self.pending_stack.push(WidgetStackItem::Node {\n            node: root_node,\n            path: vec![],\n            possible_key: \"<*>\".to_string(),\n            master_shared_props: None,\n        });\n        self.done_stack.clear();\n        while let Some(item) = self.pending_stack.pop() {\n            match item {\n                WidgetStackItem::Node {\n                    node,\n                    mut path,\n                    possible_key,\n                    master_shared_props,\n                } => match node {\n                    WidgetNode::None | WidgetNode::Tuple(_) => {\n                        self.done_stack.push(node);\n                    }\n                    WidgetNode::Component(component) => {\n                        let WidgetComponent {\n                            processor,\n                            type_name,\n                            key,\n                            mut idref,\n                            mut props,\n                            shared_props,\n                            listed_slots,\n                            named_slots,\n                        } = component;\n                        let mut shared_props = match (master_shared_props, shared_props) {\n                            (Some(master_shared_props), Some(shared_props)) => {\n                                master_shared_props.merge(shared_props)\n                            }\n                            (None, Some(shared_props)) => shared_props,\n                            (Some(master_shared_props), None) => master_shared_props,\n                            _ => Default::default(),\n                        };\n                        let key = match &key {\n                            Some(key) => key.to_owned(),\n                            None => possible_key.to_owned(),\n                        };\n                        path.push(key.clone().into());\n                        let id = WidgetId::new(&type_name, &path);\n                        used_ids.insert(id.clone());\n                        if let Some(idref) = &mut idref {\n                            idref.write(id.to_owned());\n                        }\n                        let (state_sender, state_receiver) = channel();\n                        let (animation_sender, animation_receiver) = channel();\n                        let messages_list = messages.remove(&id).unwrap_or_default();\n                        let mut life_cycle = WidgetLifeCycle::default();\n                        let default_animator_state = AnimatorStates::default();\n                        let (new_node, mounted) = match states.get(&id) {\n                            Some(state) => {\n                                let state =\n                                    State::new(state, StateUpdate::new(state_sender.clone()));\n                                let animator =\n                                    self.animators.get(&id).unwrap_or(&default_animator_state);\n                                let view_models =\n                                    ViewModelCollectionView::new(&id, &mut self.view_models);\n                                let context = WidgetContext {\n                                    id: &id,\n                                    idref: idref.as_ref(),\n                                    key: &key,\n                                    props: &mut props,\n                                    shared_props: &mut shared_props,\n                                    state,\n                                    animator,\n                                    life_cycle: &mut life_cycle,\n                                    named_slots,\n                                    listed_slots,\n                                    view_models,\n                                };\n                                (processor.call(context), false)\n                            }\n                            None => {\n                                let state_data = Props::default();\n                                let state =\n                                    State::new(&state_data, StateUpdate::new(state_sender.clone()));\n                                let animator =\n                                    self.animators.get(&id).unwrap_or(&default_animator_state);\n                                let view_models =\n                                    ViewModelCollectionView::new(&id, &mut self.view_models);\n                                let context = WidgetContext {\n                                    id: &id,\n                                    idref: idref.as_ref(),\n                                    key: &key,\n                                    props: &mut props,\n                                    shared_props: &mut shared_props,\n                                    state,\n                                    animator,\n                                    life_cycle: &mut life_cycle,\n                                    named_slots,\n                                    listed_slots,\n                                    view_models,\n                                };\n                                let node = processor.call(context);\n                                new_states.insert(id.clone(), state_data);\n                                (node, true)\n                            }\n                        };\n                        let (mount, change, unmount) = life_cycle.unwrap();\n                        if mounted {\n                            if !mount.is_empty()\n                                && let Some(state) = new_states.get(&id)\n                            {\n                                for mut closure in mount {\n                                    let state =\n                                        State::new(state, StateUpdate::new(state_sender.clone()));\n                                    let messenger =\n                                        Messenger::new(message_sender.clone(), &messages_list);\n                                    let signals =\n                                        SignalSender::new(id.clone(), signal_sender.clone());\n                                    let animator = Animator::new(\n                                        self.animators.get(&id).unwrap_or(&default_animator_state),\n                                        AnimationUpdate::new(animation_sender.clone()),\n                                    );\n                                    let view_models =\n                                        ViewModelCollectionView::new(&id, &mut self.view_models);\n                                    let context = WidgetMountOrChangeContext {\n                                        id: &id,\n                                        props: &props,\n                                        shared_props: &shared_props,\n                                        state,\n                                        messenger,\n                                        signals,\n                                        animator,\n                                        view_models,\n                                    };\n                                    (closure)(context);\n                                }\n                            }\n                        } else if !change.is_empty()\n                            && let Some(state) = states.get(&id)\n                        {\n                            for mut closure in change {\n                                let state =\n                                    State::new(state, StateUpdate::new(state_sender.clone()));\n                                let messenger =\n                                    Messenger::new(message_sender.clone(), &messages_list);\n                                let signals = SignalSender::new(id.clone(), signal_sender.clone());\n                                let animator = Animator::new(\n                                    self.animators.get(&id).unwrap_or(&default_animator_state),\n                                    AnimationUpdate::new(animation_sender.clone()),\n                                );\n                                let view_models =\n                                    ViewModelCollectionView::new(&id, &mut self.view_models);\n                                let context = WidgetMountOrChangeContext {\n                                    id: &id,\n                                    props: &props,\n                                    shared_props: &shared_props,\n                                    state,\n                                    messenger,\n                                    signals,\n                                    animator,\n                                    view_models,\n                                };\n                                (closure)(context);\n                            }\n                        }\n                        if !unmount.is_empty() {\n                            self.unmount_closures.insert(id.clone(), unmount);\n                        }\n                        while let Ok((name, data)) = animation_receiver.try_recv() {\n                            if let Some(states) = self.animators.get_mut(&id) {\n                                states.change(name, data);\n                            } else if let Some(data) = data {\n                                self.animators\n                                    .insert(id.to_owned(), AnimatorStates::new(name, data));\n                            }\n                        }\n                        while let Ok(data) = state_receiver.try_recv() {\n                            self.state_changes\n                                .entry(id.to_owned())\n                                .or_default()\n                                .push(data);\n                        }\n                        self.pending_stack.push(WidgetStackItem::Node {\n                            node: new_node,\n                            path,\n                            possible_key,\n                            master_shared_props: Some(shared_props),\n                        });\n                    }\n                    WidgetNode::Unit(unit) => match unit {\n                        WidgetUnitNode::None\n                        | WidgetUnitNode::ImageBox(_)\n                        | WidgetUnitNode::TextBox(_) => {\n                            self.done_stack.push(WidgetNode::Unit(unit));\n                        }\n                        WidgetUnitNode::AreaBox(mut unit) => {\n                            let slot = *std::mem::take(&mut unit.slot);\n                            self.pending_stack\n                                .push(WidgetStackItem::AreaBox { node: unit });\n                            self.pending_stack.push(WidgetStackItem::Node {\n                                node: slot,\n                                path,\n                                possible_key: \".\".to_owned(),\n                                master_shared_props,\n                            });\n                        }\n                        WidgetUnitNode::PortalBox(mut unit) => match &mut *unit.slot {\n                            PortalBoxSlotNode::Slot(data) => {\n                                let slot = std::mem::take(data);\n                                self.pending_stack\n                                    .push(WidgetStackItem::PortalBox { node: unit });\n                                self.pending_stack.push(WidgetStackItem::Node {\n                                    node: slot,\n                                    path,\n                                    possible_key: \".\".to_owned(),\n                                    master_shared_props,\n                                });\n                            }\n                            PortalBoxSlotNode::ContentItem(item) => {\n                                let slot = std::mem::take(&mut item.slot);\n                                self.pending_stack\n                                    .push(WidgetStackItem::PortalBox { node: unit });\n                                self.pending_stack.push(WidgetStackItem::Node {\n                                    node: slot,\n                                    path,\n                                    possible_key: \".\".to_owned(),\n                                    master_shared_props,\n                                });\n                            }\n                            PortalBoxSlotNode::FlexItem(item) => {\n                                let slot = std::mem::take(&mut item.slot);\n                                self.pending_stack\n                                    .push(WidgetStackItem::PortalBox { node: unit });\n                                self.pending_stack.push(WidgetStackItem::Node {\n                                    node: slot,\n                                    path,\n                                    possible_key: \".\".to_owned(),\n                                    master_shared_props,\n                                });\n                            }\n                            PortalBoxSlotNode::GridItem(item) => {\n                                let slot = std::mem::take(&mut item.slot);\n                                self.pending_stack\n                                    .push(WidgetStackItem::PortalBox { node: unit });\n                                self.pending_stack.push(WidgetStackItem::Node {\n                                    node: slot,\n                                    path,\n                                    possible_key: \".\".to_owned(),\n                                    master_shared_props,\n                                });\n                            }\n                        },\n                        WidgetUnitNode::ContentBox(mut unit) => {\n                            let items = unit\n                                .items\n                                .iter_mut()\n                                .map(|node| std::mem::take(&mut node.slot))\n                                .collect::<Vec<_>>();\n                            self.pending_stack\n                                .push(WidgetStackItem::ContentBox { node: unit });\n                            for (index, node) in items.into_iter().enumerate() {\n                                self.pending_stack.push(WidgetStackItem::Node {\n                                    node,\n                                    path: path.clone(),\n                                    possible_key: format!(\"<{index}>\"),\n                                    master_shared_props: master_shared_props.clone(),\n                                });\n                            }\n                        }\n                        WidgetUnitNode::FlexBox(mut unit) => {\n                            let items = unit\n                                .items\n                                .iter_mut()\n                                .map(|node| std::mem::take(&mut node.slot))\n                                .collect::<Vec<_>>();\n                            self.pending_stack\n                                .push(WidgetStackItem::FlexBox { node: unit });\n                            for (index, node) in items.into_iter().enumerate() {\n                                self.pending_stack.push(WidgetStackItem::Node {\n                                    node,\n                                    path: path.clone(),\n                                    possible_key: format!(\"<{index}>\"),\n                                    master_shared_props: master_shared_props.clone(),\n                                });\n                            }\n                        }\n                        WidgetUnitNode::GridBox(mut unit) => {\n                            let items = unit\n                                .items\n                                .iter_mut()\n                                .map(|node| std::mem::take(&mut node.slot))\n                                .collect::<Vec<_>>();\n                            self.pending_stack\n                                .push(WidgetStackItem::GridBox { node: unit });\n                            for (index, node) in items.into_iter().enumerate() {\n                                self.pending_stack.push(WidgetStackItem::Node {\n                                    node,\n                                    path: path.clone(),\n                                    possible_key: format!(\"<{index}>\"),\n                                    master_shared_props: master_shared_props.clone(),\n                                });\n                            }\n                        }\n                        WidgetUnitNode::SizeBox(mut unit) => {\n                            let slot = *std::mem::take(&mut unit.slot);\n                            self.pending_stack\n                                .push(WidgetStackItem::SizeBox { node: unit });\n                            self.pending_stack.push(WidgetStackItem::Node {\n                                node: slot,\n                                path,\n                                possible_key: \".\".to_owned(),\n                                master_shared_props,\n                            });\n                        }\n                    },\n                },\n                WidgetStackItem::AreaBox { mut node } => {\n                    node.slot = Box::new(self.done_stack.pop().unwrap_or_default());\n                    self.done_stack\n                        .push(WidgetNode::Unit(WidgetUnitNode::AreaBox(node)));\n                }\n                WidgetStackItem::PortalBox { mut node } => {\n                    match &mut *node.slot {\n                        PortalBoxSlotNode::Slot(node) => {\n                            *node = self.done_stack.pop().unwrap_or_default();\n                        }\n                        PortalBoxSlotNode::ContentItem(node) => {\n                            node.slot = self.done_stack.pop().unwrap_or_default();\n                        }\n                        PortalBoxSlotNode::FlexItem(node) => {\n                            node.slot = self.done_stack.pop().unwrap_or_default();\n                        }\n                        PortalBoxSlotNode::GridItem(node) => {\n                            node.slot = self.done_stack.pop().unwrap_or_default();\n                        }\n                    }\n                    self.done_stack\n                        .push(WidgetNode::Unit(WidgetUnitNode::PortalBox(node)));\n                }\n                WidgetStackItem::ContentBox { mut node } => {\n                    for item in node.items.iter_mut() {\n                        item.slot = self.done_stack.pop().unwrap_or_default();\n                    }\n                    self.done_stack\n                        .push(WidgetNode::Unit(WidgetUnitNode::ContentBox(node)));\n                }\n                WidgetStackItem::FlexBox { mut node } => {\n                    for item in node.items.iter_mut() {\n                        item.slot = self.done_stack.pop().unwrap_or_default();\n                    }\n                    self.done_stack\n                        .push(WidgetNode::Unit(WidgetUnitNode::FlexBox(node)));\n                }\n                WidgetStackItem::GridBox { mut node } => {\n                    for item in node.items.iter_mut() {\n                        item.slot = self.done_stack.pop().unwrap_or_default();\n                    }\n                    self.done_stack\n                        .push(WidgetNode::Unit(WidgetUnitNode::GridBox(node)));\n                }\n                WidgetStackItem::SizeBox { mut node } => {\n                    node.slot = Box::new(self.done_stack.pop().unwrap_or_default());\n                    self.done_stack\n                        .push(WidgetNode::Unit(WidgetUnitNode::SizeBox(node)));\n                }\n            }\n        }\n        assert!(\n            self.pending_stack.is_empty(),\n            \"Pending stack should be empty after processing\"\n        );\n        assert_eq!(\n            self.done_stack.len(),\n            1,\n            \"Done stack should have exactly one item after processing\"\n        );\n        self.done_stack.pop().unwrap_or_default()\n    }\n\n    fn teleport_portals(mut root: WidgetUnit) -> WidgetUnit {\n        let count = Self::estimate_portals(&root);\n        if count == 0 {\n            return root;\n        }\n        let mut portals = Vec::with_capacity(count);\n        Self::consume_portals(&mut root, &mut portals);\n        Self::inject_portals(&mut root, &mut portals);\n        root\n    }\n\n    fn estimate_portals(unit: &WidgetUnit) -> usize {\n        let mut count = 0;\n        match unit {\n            WidgetUnit::None | WidgetUnit::ImageBox(_) | WidgetUnit::TextBox(_) => {}\n            WidgetUnit::AreaBox(b) => count += Self::estimate_portals(&b.slot),\n            WidgetUnit::PortalBox(b) => {\n                count += Self::estimate_portals(match &*b.slot {\n                    PortalBoxSlot::Slot(slot) => slot,\n                    PortalBoxSlot::ContentItem(item) => &item.slot,\n                    PortalBoxSlot::FlexItem(item) => &item.slot,\n                    PortalBoxSlot::GridItem(item) => &item.slot,\n                }) + 1\n            }\n            WidgetUnit::ContentBox(b) => {\n                for item in &b.items {\n                    count += Self::estimate_portals(&item.slot);\n                }\n            }\n            WidgetUnit::FlexBox(b) => {\n                for item in &b.items {\n                    count += Self::estimate_portals(&item.slot);\n                }\n            }\n            WidgetUnit::GridBox(b) => {\n                for item in &b.items {\n                    count += Self::estimate_portals(&item.slot);\n                }\n            }\n            WidgetUnit::SizeBox(b) => count += Self::estimate_portals(&b.slot),\n        }\n        count\n    }\n\n    fn consume_portals(unit: &mut WidgetUnit, bucket: &mut Vec<(WidgetId, PortalBoxSlot)>) {\n        match unit {\n            WidgetUnit::None | WidgetUnit::ImageBox(_) | WidgetUnit::TextBox(_) => {}\n            WidgetUnit::AreaBox(b) => Self::consume_portals(&mut b.slot, bucket),\n            WidgetUnit::PortalBox(b) => {\n                let PortalBox {\n                    owner, mut slot, ..\n                } = std::mem::take(b);\n                Self::consume_portals(\n                    match &mut *slot {\n                        PortalBoxSlot::Slot(slot) => slot,\n                        PortalBoxSlot::ContentItem(item) => &mut item.slot,\n                        PortalBoxSlot::FlexItem(item) => &mut item.slot,\n                        PortalBoxSlot::GridItem(item) => &mut item.slot,\n                    },\n                    bucket,\n                );\n                bucket.push((owner, *slot));\n            }\n            WidgetUnit::ContentBox(b) => {\n                for item in &mut b.items {\n                    Self::consume_portals(&mut item.slot, bucket);\n                }\n            }\n            WidgetUnit::FlexBox(b) => {\n                for item in &mut b.items {\n                    Self::consume_portals(&mut item.slot, bucket);\n                }\n            }\n            WidgetUnit::GridBox(b) => {\n                for item in &mut b.items {\n                    Self::consume_portals(&mut item.slot, bucket);\n                }\n            }\n            WidgetUnit::SizeBox(b) => Self::consume_portals(&mut b.slot, bucket),\n        }\n    }\n\n    fn inject_portals(unit: &mut WidgetUnit, portals: &mut Vec<(WidgetId, PortalBoxSlot)>) -> bool {\n        if portals.is_empty() {\n            return false;\n        }\n        while let Some(data) = unit.as_data() {\n            let found = portals.iter().position(|(id, _)| data.id() == id);\n            if let Some(index) = found {\n                let slot = portals.swap_remove(index).1;\n                match unit {\n                    WidgetUnit::None\n                    | WidgetUnit::PortalBox(_)\n                    | WidgetUnit::ImageBox(_)\n                    | WidgetUnit::TextBox(_) => {}\n                    WidgetUnit::AreaBox(b) => {\n                        match slot {\n                            PortalBoxSlot::Slot(slot) => *b.slot = slot,\n                            PortalBoxSlot::ContentItem(item) => *b.slot = item.slot,\n                            PortalBoxSlot::FlexItem(item) => *b.slot = item.slot,\n                            PortalBoxSlot::GridItem(item) => *b.slot = item.slot,\n                        }\n                        if !Self::inject_portals(&mut b.slot, portals) {\n                            return false;\n                        }\n                    }\n                    WidgetUnit::ContentBox(b) => {\n                        b.items.push(match slot {\n                            PortalBoxSlot::Slot(slot) => ContentBoxItem {\n                                slot,\n                                ..Default::default()\n                            },\n                            PortalBoxSlot::ContentItem(item) => item,\n                            PortalBoxSlot::FlexItem(item) => ContentBoxItem {\n                                slot: item.slot,\n                                ..Default::default()\n                            },\n                            PortalBoxSlot::GridItem(item) => ContentBoxItem {\n                                slot: item.slot,\n                                ..Default::default()\n                            },\n                        });\n                        for item in &mut b.items {\n                            if !Self::inject_portals(&mut item.slot, portals) {\n                                return false;\n                            }\n                        }\n                    }\n                    WidgetUnit::FlexBox(b) => {\n                        b.items.push(match slot {\n                            PortalBoxSlot::Slot(slot) => FlexBoxItem {\n                                slot,\n                                ..Default::default()\n                            },\n                            PortalBoxSlot::ContentItem(item) => FlexBoxItem {\n                                slot: item.slot,\n                                ..Default::default()\n                            },\n                            PortalBoxSlot::FlexItem(item) => item,\n                            PortalBoxSlot::GridItem(item) => FlexBoxItem {\n                                slot: item.slot,\n                                ..Default::default()\n                            },\n                        });\n                        for item in &mut b.items {\n                            if !Self::inject_portals(&mut item.slot, portals) {\n                                return false;\n                            }\n                        }\n                    }\n                    WidgetUnit::GridBox(b) => {\n                        b.items.push(match slot {\n                            PortalBoxSlot::Slot(slot) => GridBoxItem {\n                                slot,\n                                ..Default::default()\n                            },\n                            PortalBoxSlot::ContentItem(item) => GridBoxItem {\n                                slot: item.slot,\n                                ..Default::default()\n                            },\n                            PortalBoxSlot::FlexItem(item) => GridBoxItem {\n                                slot: item.slot,\n                                ..Default::default()\n                            },\n                            PortalBoxSlot::GridItem(item) => item,\n                        });\n                        for item in &mut b.items {\n                            if !Self::inject_portals(&mut item.slot, portals) {\n                                return false;\n                            }\n                        }\n                    }\n                    WidgetUnit::SizeBox(b) => {\n                        match slot {\n                            PortalBoxSlot::Slot(slot) => *b.slot = slot,\n                            PortalBoxSlot::ContentItem(item) => *b.slot = item.slot,\n                            PortalBoxSlot::FlexItem(item) => *b.slot = item.slot,\n                            PortalBoxSlot::GridItem(item) => *b.slot = item.slot,\n                        }\n                        if !Self::inject_portals(&mut b.slot, portals) {\n                            return false;\n                        }\n                    }\n                }\n            } else {\n                break;\n            }\n        }\n        true\n    }\n\n    fn node_to_prefab(&self, data: &WidgetNode) -> Result<WidgetNodePrefab, ApplicationError> {\n        Ok(match data {\n            WidgetNode::None => WidgetNodePrefab::None,\n            WidgetNode::Component(data) => {\n                WidgetNodePrefab::Component(self.component_to_prefab(data)?)\n            }\n            WidgetNode::Unit(data) => WidgetNodePrefab::Unit(self.unit_to_prefab(data)?),\n            WidgetNode::Tuple(data) => WidgetNodePrefab::Tuple(self.tuple_to_prefab(data)?),\n        })\n    }\n\n    fn component_to_prefab(\n        &self,\n        data: &WidgetComponent,\n    ) -> Result<WidgetComponentPrefab, ApplicationError> {\n        if self.component_mappings.contains_key(&data.type_name) {\n            Ok(WidgetComponentPrefab {\n                type_name: data.type_name.to_owned(),\n                key: data.key.clone(),\n                props: self.props_registry.serialize(&data.props)?,\n                shared_props: match &data.shared_props {\n                    Some(p) => Some(self.props_registry.serialize(p)?),\n                    None => None,\n                },\n                listed_slots: data\n                    .listed_slots\n                    .iter()\n                    .map(|v| self.node_to_prefab(v))\n                    .collect::<Result<_, _>>()?,\n                named_slots: data\n                    .named_slots\n                    .iter()\n                    .map(|(k, v)| Ok((k.to_owned(), self.node_to_prefab(v)?)))\n                    .collect::<Result<_, ApplicationError>>()?,\n            })\n        } else {\n            Err(ApplicationError::ComponentMappingNotFound(\n                data.type_name.to_owned(),\n            ))\n        }\n    }\n\n    fn unit_to_prefab(\n        &self,\n        data: &WidgetUnitNode,\n    ) -> Result<WidgetUnitNodePrefab, ApplicationError> {\n        Ok(match data {\n            WidgetUnitNode::None => WidgetUnitNodePrefab::None,\n            WidgetUnitNode::AreaBox(data) => {\n                WidgetUnitNodePrefab::AreaBox(self.area_box_to_prefab(data)?)\n            }\n            WidgetUnitNode::PortalBox(data) => {\n                WidgetUnitNodePrefab::PortalBox(self.portal_box_to_prefab(data)?)\n            }\n            WidgetUnitNode::ContentBox(data) => {\n                WidgetUnitNodePrefab::ContentBox(self.content_box_to_prefab(data)?)\n            }\n            WidgetUnitNode::FlexBox(data) => {\n                WidgetUnitNodePrefab::FlexBox(self.flex_box_to_prefab(data)?)\n            }\n            WidgetUnitNode::GridBox(data) => {\n                WidgetUnitNodePrefab::GridBox(self.grid_box_to_prefab(data)?)\n            }\n            WidgetUnitNode::SizeBox(data) => {\n                WidgetUnitNodePrefab::SizeBox(self.size_box_to_prefab(data)?)\n            }\n            WidgetUnitNode::ImageBox(data) => {\n                WidgetUnitNodePrefab::ImageBox(self.image_box_to_prefab(data)?)\n            }\n            WidgetUnitNode::TextBox(data) => {\n                WidgetUnitNodePrefab::TextBox(self.text_box_to_prefab(data)?)\n            }\n        })\n    }\n\n    fn tuple_to_prefab(\n        &self,\n        data: &[WidgetNode],\n    ) -> Result<Vec<WidgetNodePrefab>, ApplicationError> {\n        data.iter()\n            .map(|node| self.node_to_prefab(node))\n            .collect::<Result<_, _>>()\n    }\n\n    fn area_box_to_prefab(\n        &self,\n        data: &AreaBoxNode,\n    ) -> Result<AreaBoxNodePrefab, ApplicationError> {\n        Ok(AreaBoxNodePrefab {\n            id: data.id.to_owned(),\n            slot: Box::new(self.node_to_prefab(&data.slot)?),\n        })\n    }\n\n    fn portal_box_to_prefab(\n        &self,\n        data: &PortalBoxNode,\n    ) -> Result<PortalBoxNodePrefab, ApplicationError> {\n        Ok(PortalBoxNodePrefab {\n            id: data.id.to_owned(),\n            slot: Box::new(match &*data.slot {\n                PortalBoxSlotNode::Slot(slot) => {\n                    PortalBoxSlotNodePrefab::Slot(self.node_to_prefab(slot)?)\n                }\n                PortalBoxSlotNode::ContentItem(item) => {\n                    PortalBoxSlotNodePrefab::ContentItem(ContentBoxItemNodePrefab {\n                        slot: self.node_to_prefab(&item.slot)?,\n                        layout: item.layout.clone(),\n                    })\n                }\n                PortalBoxSlotNode::FlexItem(item) => {\n                    PortalBoxSlotNodePrefab::FlexItem(FlexBoxItemNodePrefab {\n                        slot: self.node_to_prefab(&item.slot)?,\n                        layout: item.layout.clone(),\n                    })\n                }\n                PortalBoxSlotNode::GridItem(item) => {\n                    PortalBoxSlotNodePrefab::GridItem(GridBoxItemNodePrefab {\n                        slot: self.node_to_prefab(&item.slot)?,\n                        layout: item.layout.clone(),\n                    })\n                }\n            }),\n            owner: data.owner.to_owned(),\n        })\n    }\n\n    fn content_box_to_prefab(\n        &self,\n        data: &ContentBoxNode,\n    ) -> Result<ContentBoxNodePrefab, ApplicationError> {\n        Ok(ContentBoxNodePrefab {\n            id: data.id.to_owned(),\n            props: self.props_registry.serialize(&data.props)?,\n            items: data\n                .items\n                .iter()\n                .map(|v| {\n                    Ok(ContentBoxItemNodePrefab {\n                        slot: self.node_to_prefab(&v.slot)?,\n                        layout: v.layout.clone(),\n                    })\n                })\n                .collect::<Result<_, ApplicationError>>()?,\n            clipping: data.clipping,\n            content_reposition: data.content_reposition,\n            transform: data.transform,\n        })\n    }\n\n    fn flex_box_to_prefab(\n        &self,\n        data: &FlexBoxNode,\n    ) -> Result<FlexBoxNodePrefab, ApplicationError> {\n        Ok(FlexBoxNodePrefab {\n            id: data.id.to_owned(),\n            props: self.props_registry.serialize(&data.props)?,\n            items: data\n                .items\n                .iter()\n                .map(|v| {\n                    Ok(FlexBoxItemNodePrefab {\n                        slot: self.node_to_prefab(&v.slot)?,\n                        layout: v.layout.clone(),\n                    })\n                })\n                .collect::<Result<_, ApplicationError>>()?,\n            direction: data.direction,\n            separation: data.separation,\n            wrap: data.wrap,\n            transform: data.transform,\n        })\n    }\n\n    fn grid_box_to_prefab(\n        &self,\n        data: &GridBoxNode,\n    ) -> Result<GridBoxNodePrefab, ApplicationError> {\n        Ok(GridBoxNodePrefab {\n            id: data.id.to_owned(),\n            props: self.props_registry.serialize(&data.props)?,\n            items: data\n                .items\n                .iter()\n                .map(|v| {\n                    Ok(GridBoxItemNodePrefab {\n                        slot: self.node_to_prefab(&v.slot)?,\n                        layout: v.layout.clone(),\n                    })\n                })\n                .collect::<Result<_, ApplicationError>>()?,\n            cols: data.cols,\n            rows: data.rows,\n            transform: data.transform,\n        })\n    }\n\n    fn size_box_to_prefab(\n        &self,\n        data: &SizeBoxNode,\n    ) -> Result<SizeBoxNodePrefab, ApplicationError> {\n        Ok(SizeBoxNodePrefab {\n            id: data.id.to_owned(),\n            props: self.props_registry.serialize(&data.props)?,\n            slot: Box::new(self.node_to_prefab(&data.slot)?),\n            width: data.width,\n            height: data.height,\n            margin: data.margin,\n            keep_aspect_ratio: data.keep_aspect_ratio,\n            transform: data.transform,\n        })\n    }\n\n    fn image_box_to_prefab(\n        &self,\n        data: &ImageBoxNode,\n    ) -> Result<ImageBoxNodePrefab, ApplicationError> {\n        Ok(ImageBoxNodePrefab {\n            id: data.id.to_owned(),\n            props: self.props_registry.serialize(&data.props)?,\n            width: data.width,\n            height: data.height,\n            content_keep_aspect_ratio: data.content_keep_aspect_ratio,\n            material: data.material.clone(),\n            transform: data.transform,\n        })\n    }\n\n    fn text_box_to_prefab(\n        &self,\n        data: &TextBoxNode,\n    ) -> Result<TextBoxNodePrefab, ApplicationError> {\n        Ok(TextBoxNodePrefab {\n            id: data.id.to_owned(),\n            props: self.props_registry.serialize(&data.props)?,\n            text: data.text.clone(),\n            width: data.width,\n            height: data.height,\n            horizontal_align: data.horizontal_align,\n            vertical_align: data.vertical_align,\n            direction: data.direction,\n            font: data.font.clone(),\n            color: data.color,\n            transform: data.transform,\n        })\n    }\n\n    fn node_from_prefab(&self, data: WidgetNodePrefab) -> Result<WidgetNode, ApplicationError> {\n        Ok(match data {\n            WidgetNodePrefab::None => WidgetNode::None,\n            WidgetNodePrefab::Component(data) => {\n                WidgetNode::Component(self.component_from_prefab(data)?)\n            }\n            WidgetNodePrefab::Unit(data) => WidgetNode::Unit(self.unit_from_prefab(data)?),\n            WidgetNodePrefab::Tuple(data) => WidgetNode::Tuple(self.tuple_from_prefab(data)?),\n        })\n    }\n\n    fn component_from_prefab(\n        &self,\n        data: WidgetComponentPrefab,\n    ) -> Result<WidgetComponent, ApplicationError> {\n        if let Some(processor) = self.component_mappings.get(&data.type_name) {\n            Ok(WidgetComponent {\n                processor: processor.clone(),\n                type_name: data.type_name,\n                key: data.key,\n                idref: Default::default(),\n                props: self.deserialize_props(data.props)?,\n                shared_props: match data.shared_props {\n                    Some(p) => Some(self.deserialize_props(p)?),\n                    None => None,\n                },\n                listed_slots: data\n                    .listed_slots\n                    .into_iter()\n                    .map(|v| self.node_from_prefab(v))\n                    .collect::<Result<_, ApplicationError>>()?,\n                named_slots: data\n                    .named_slots\n                    .into_iter()\n                    .map(|(k, v)| Ok((k, self.node_from_prefab(v)?)))\n                    .collect::<Result<_, ApplicationError>>()?,\n            })\n        } else {\n            Err(ApplicationError::ComponentMappingNotFound(\n                data.type_name.clone(),\n            ))\n        }\n    }\n\n    fn unit_from_prefab(\n        &self,\n        data: WidgetUnitNodePrefab,\n    ) -> Result<WidgetUnitNode, ApplicationError> {\n        Ok(match data {\n            WidgetUnitNodePrefab::None => WidgetUnitNode::None,\n            WidgetUnitNodePrefab::AreaBox(data) => {\n                WidgetUnitNode::AreaBox(self.area_box_from_prefab(data)?)\n            }\n            WidgetUnitNodePrefab::PortalBox(data) => {\n                WidgetUnitNode::PortalBox(self.portal_box_from_prefab(data)?)\n            }\n            WidgetUnitNodePrefab::ContentBox(data) => {\n                WidgetUnitNode::ContentBox(self.content_box_from_prefab(data)?)\n            }\n            WidgetUnitNodePrefab::FlexBox(data) => {\n                WidgetUnitNode::FlexBox(self.flex_box_from_prefab(data)?)\n            }\n            WidgetUnitNodePrefab::GridBox(data) => {\n                WidgetUnitNode::GridBox(self.grid_box_from_prefab(data)?)\n            }\n            WidgetUnitNodePrefab::SizeBox(data) => {\n                WidgetUnitNode::SizeBox(self.size_box_from_prefab(data)?)\n            }\n            WidgetUnitNodePrefab::ImageBox(data) => {\n                WidgetUnitNode::ImageBox(self.image_box_from_prefab(data)?)\n            }\n            WidgetUnitNodePrefab::TextBox(data) => {\n                WidgetUnitNode::TextBox(self.text_box_from_prefab(data)?)\n            }\n        })\n    }\n\n    fn tuple_from_prefab(\n        &self,\n        data: Vec<WidgetNodePrefab>,\n    ) -> Result<Vec<WidgetNode>, ApplicationError> {\n        data.into_iter()\n            .map(|data| self.node_from_prefab(data))\n            .collect::<Result<_, _>>()\n    }\n\n    fn area_box_from_prefab(\n        &self,\n        data: AreaBoxNodePrefab,\n    ) -> Result<AreaBoxNode, ApplicationError> {\n        Ok(AreaBoxNode {\n            id: data.id,\n            slot: Box::new(self.node_from_prefab(*data.slot)?),\n        })\n    }\n\n    fn portal_box_from_prefab(\n        &self,\n        data: PortalBoxNodePrefab,\n    ) -> Result<PortalBoxNode, ApplicationError> {\n        Ok(PortalBoxNode {\n            id: data.id,\n            slot: Box::new(match *data.slot {\n                PortalBoxSlotNodePrefab::Slot(slot) => {\n                    PortalBoxSlotNode::Slot(self.node_from_prefab(slot)?)\n                }\n                PortalBoxSlotNodePrefab::ContentItem(item) => {\n                    PortalBoxSlotNode::ContentItem(ContentBoxItemNode {\n                        slot: self.node_from_prefab(item.slot)?,\n                        layout: item.layout,\n                    })\n                }\n                PortalBoxSlotNodePrefab::FlexItem(item) => {\n                    PortalBoxSlotNode::FlexItem(FlexBoxItemNode {\n                        slot: self.node_from_prefab(item.slot)?,\n                        layout: item.layout,\n                    })\n                }\n                PortalBoxSlotNodePrefab::GridItem(item) => {\n                    PortalBoxSlotNode::GridItem(GridBoxItemNode {\n                        slot: self.node_from_prefab(item.slot)?,\n                        layout: item.layout,\n                    })\n                }\n            }),\n            owner: data.owner,\n        })\n    }\n\n    fn content_box_from_prefab(\n        &self,\n        data: ContentBoxNodePrefab,\n    ) -> Result<ContentBoxNode, ApplicationError> {\n        Ok(ContentBoxNode {\n            id: data.id,\n            props: self.props_registry.deserialize(data.props)?,\n            items: data\n                .items\n                .into_iter()\n                .map(|v| {\n                    Ok(ContentBoxItemNode {\n                        slot: self.node_from_prefab(v.slot)?,\n                        layout: v.layout,\n                    })\n                })\n                .collect::<Result<_, ApplicationError>>()?,\n            clipping: data.clipping,\n            content_reposition: data.content_reposition,\n            transform: data.transform,\n        })\n    }\n\n    fn flex_box_from_prefab(\n        &self,\n        data: FlexBoxNodePrefab,\n    ) -> Result<FlexBoxNode, ApplicationError> {\n        Ok(FlexBoxNode {\n            id: data.id,\n            props: self.props_registry.deserialize(data.props)?,\n            items: data\n                .items\n                .into_iter()\n                .map(|v| {\n                    Ok(FlexBoxItemNode {\n                        slot: self.node_from_prefab(v.slot)?,\n                        layout: v.layout,\n                    })\n                })\n                .collect::<Result<_, ApplicationError>>()?,\n            direction: data.direction,\n            separation: data.separation,\n            wrap: data.wrap,\n            transform: data.transform,\n        })\n    }\n\n    fn grid_box_from_prefab(\n        &self,\n        data: GridBoxNodePrefab,\n    ) -> Result<GridBoxNode, ApplicationError> {\n        Ok(GridBoxNode {\n            id: data.id,\n            props: self.props_registry.deserialize(data.props)?,\n            items: data\n                .items\n                .into_iter()\n                .map(|v| {\n                    Ok(GridBoxItemNode {\n                        slot: self.node_from_prefab(v.slot)?,\n                        layout: v.layout,\n                    })\n                })\n                .collect::<Result<_, ApplicationError>>()?,\n            cols: data.cols,\n            rows: data.rows,\n            transform: data.transform,\n        })\n    }\n\n    fn size_box_from_prefab(\n        &self,\n        data: SizeBoxNodePrefab,\n    ) -> Result<SizeBoxNode, ApplicationError> {\n        Ok(SizeBoxNode {\n            id: data.id,\n            props: self.props_registry.deserialize(data.props)?,\n            slot: Box::new(self.node_from_prefab(*data.slot)?),\n            width: data.width,\n            height: data.height,\n            margin: data.margin,\n            keep_aspect_ratio: data.keep_aspect_ratio,\n            transform: data.transform,\n        })\n    }\n\n    fn image_box_from_prefab(\n        &self,\n        data: ImageBoxNodePrefab,\n    ) -> Result<ImageBoxNode, ApplicationError> {\n        Ok(ImageBoxNode {\n            id: data.id,\n            props: self.props_registry.deserialize(data.props)?,\n            width: data.width,\n            height: data.height,\n            content_keep_aspect_ratio: data.content_keep_aspect_ratio,\n            material: data.material,\n            transform: data.transform,\n        })\n    }\n\n    fn text_box_from_prefab(\n        &self,\n        data: TextBoxNodePrefab,\n    ) -> Result<TextBoxNode, ApplicationError> {\n        Ok(TextBoxNode {\n            id: data.id,\n            props: self.props_registry.deserialize(data.props)?,\n            text: data.text,\n            width: data.width,\n            height: data.height,\n            horizontal_align: data.horizontal_align,\n            vertical_align: data.vertical_align,\n            direction: data.direction,\n            font: data.font,\n            color: data.color,\n            transform: data.transform,\n        })\n    }\n}\n\n#[allow(clippy::large_enum_variant)]\nenum WidgetStackItem {\n    Node {\n        node: WidgetNode,\n        path: Vec<Cow<'static, str>>,\n        possible_key: String,\n        master_shared_props: Option<Props>,\n    },\n    AreaBox {\n        node: AreaBoxNode,\n    },\n    PortalBox {\n        node: PortalBoxNode,\n    },\n    ContentBox {\n        node: ContentBoxNode,\n    },\n    FlexBox {\n        node: FlexBoxNode,\n    },\n    GridBox {\n        node: GridBoxNode,\n    },\n    SizeBox {\n        node: SizeBoxNode,\n    },\n}\n"
  },
  {
    "path": "crates/core/src/interactive/default_interactions_engine.rs",
    "content": "use crate::{\n    Scalar,\n    application::Application,\n    interactive::InteractionsEngine,\n    messenger::MessageData,\n    widget::{\n        WidgetId,\n        component::{\n            RelativeLayoutListenerSignal, ResizeListenerSignal,\n            interactive::navigation::{NavDirection, NavJump, NavScroll, NavSignal, NavType},\n        },\n        unit::WidgetUnit,\n        utils::{Rect, Vec2, lerp},\n    },\n};\nuse std::collections::{HashMap, HashSet, VecDeque};\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq)]\npub enum PointerButton {\n    Trigger,\n    Context,\n}\n\n#[derive(Debug, Default, Clone)]\npub enum Interaction {\n    #[default]\n    None,\n    Navigate(NavSignal),\n    PointerDown(PointerButton, Vec2),\n    PointerUp(PointerButton, Vec2),\n    PointerMove(Vec2),\n}\n\nimpl Interaction {\n    pub fn is_none(&self) -> bool {\n        matches!(self, Self::None)\n    }\n\n    #[inline]\n    pub fn is_some(&self) -> bool {\n        !self.is_none()\n    }\n}\n\n#[derive(Debug, Default, Copy, Clone)]\npub struct DefaultInteractionsEngineResult {\n    pub captured_pointer_location: bool,\n    pub captured_pointer_action: bool,\n    pub captured_text_change: bool,\n}\n\nimpl DefaultInteractionsEngineResult {\n    #[inline]\n    pub fn is_any(&self) -> bool {\n        self.captured_pointer_action || self.captured_pointer_location || self.captured_text_change\n    }\n\n    #[inline]\n    pub fn is_none(&self) -> bool {\n        !self.is_any()\n    }\n}\n\n/// Single pointer + Keyboard + Gamepad\n#[derive(Debug, Default)]\npub struct DefaultInteractionsEngine {\n    pub deselect_when_no_button_found: bool,\n    pub unfocus_when_selection_change: bool,\n    resize_listeners: HashMap<WidgetId, Vec2>,\n    relative_layout_listeners: HashMap<WidgetId, (WidgetId, Vec2, Rect)>,\n    interactions_queue: VecDeque<Interaction>,\n    containers: HashMap<WidgetId, HashSet<WidgetId>>,\n    items_owners: HashMap<WidgetId, WidgetId>,\n    buttons: HashSet<WidgetId>,\n    text_inputs: HashSet<WidgetId>,\n    scroll_views: HashSet<WidgetId>,\n    scroll_view_contents: HashSet<WidgetId>,\n    tracking: HashMap<WidgetId, WidgetId>,\n    selected_chain: Vec<WidgetId>,\n    locked_widget: Option<WidgetId>,\n    focused_text_input: Option<WidgetId>,\n    sorted_items_ids: Vec<WidgetId>,\n}\n\nimpl DefaultInteractionsEngine {\n    #[allow(clippy::too_many_arguments)]\n    pub fn with_capacity(\n        resize_listeners: usize,\n        relative_layout_listeners: usize,\n        interactions_queue: usize,\n        containers: usize,\n        buttons: usize,\n        text_inputs: usize,\n        scroll_views: usize,\n        tracking: usize,\n        selected_chain: usize,\n    ) -> Self {\n        Self {\n            deselect_when_no_button_found: false,\n            unfocus_when_selection_change: true,\n            resize_listeners: HashMap::with_capacity(resize_listeners),\n            relative_layout_listeners: HashMap::with_capacity(relative_layout_listeners),\n            interactions_queue: VecDeque::with_capacity(interactions_queue),\n            containers: HashMap::with_capacity(containers),\n            items_owners: Default::default(),\n            buttons: HashSet::with_capacity(buttons),\n            text_inputs: HashSet::with_capacity(text_inputs),\n            scroll_views: HashSet::with_capacity(scroll_views),\n            scroll_view_contents: HashSet::with_capacity(scroll_views),\n            tracking: HashMap::with_capacity(tracking),\n            selected_chain: Vec::with_capacity(selected_chain),\n            locked_widget: None,\n            focused_text_input: None,\n            sorted_items_ids: vec![],\n        }\n    }\n\n    pub fn locked_widget(&self) -> Option<&WidgetId> {\n        self.locked_widget.as_ref()\n    }\n\n    pub fn selected_chain(&self) -> &[WidgetId] {\n        &self.selected_chain\n    }\n\n    pub fn selected_item(&self) -> Option<&WidgetId> {\n        self.selected_chain.last()\n    }\n\n    pub fn selected_container(&self) -> Option<&WidgetId> {\n        self.selected_chain\n            .iter()\n            .rev()\n            .find(|id| self.containers.contains_key(id))\n    }\n\n    pub fn selected_button(&self) -> Option<&WidgetId> {\n        self.selected_chain\n            .iter()\n            .rev()\n            .find(|id| self.buttons.contains(id))\n    }\n\n    pub fn selected_scroll_view(&self) -> Option<&WidgetId> {\n        self.selected_chain\n            .iter()\n            .rev()\n            .find(|id| self.scroll_views.contains(id))\n    }\n\n    pub fn selected_scroll_view_content(&self) -> Option<&WidgetId> {\n        self.selected_chain\n            .iter()\n            .rev()\n            .find(|id| self.scroll_view_contents.contains(id))\n    }\n\n    pub fn focused_text_input(&self) -> Option<&WidgetId> {\n        self.focused_text_input.as_ref()\n    }\n\n    pub fn interact(&mut self, interaction: Interaction) {\n        if interaction.is_some() {\n            self.interactions_queue.push_back(interaction);\n        }\n    }\n\n    pub fn clear_queue(&mut self, put_unselect: bool) {\n        self.interactions_queue.clear();\n        if put_unselect {\n            self.interactions_queue\n                .push_back(Interaction::Navigate(NavSignal::Unselect));\n        }\n    }\n\n    fn cache_sorted_items_ids(&mut self, app: &Application) {\n        self.sorted_items_ids = Vec::with_capacity(self.items_owners.len());\n        self.cache_sorted_items_ids_inner(app.rendered_tree());\n    }\n\n    fn cache_sorted_items_ids_inner(&mut self, unit: &WidgetUnit) {\n        if let Some(data) = unit.as_data() {\n            self.sorted_items_ids.push(data.id().to_owned());\n        }\n        match unit {\n            WidgetUnit::AreaBox(unit) => {\n                self.cache_sorted_items_ids_inner(&unit.slot);\n            }\n            WidgetUnit::ContentBox(unit) => {\n                for item in &unit.items {\n                    self.cache_sorted_items_ids_inner(&item.slot);\n                }\n            }\n            WidgetUnit::FlexBox(unit) => {\n                if unit.direction.is_order_ascending() {\n                    for item in &unit.items {\n                        self.cache_sorted_items_ids_inner(&item.slot);\n                    }\n                } else {\n                    for item in unit.items.iter().rev() {\n                        self.cache_sorted_items_ids_inner(&item.slot);\n                    }\n                }\n            }\n            WidgetUnit::GridBox(unit) => {\n                for item in &unit.items {\n                    self.cache_sorted_items_ids_inner(&item.slot);\n                }\n            }\n            WidgetUnit::SizeBox(unit) => {\n                self.cache_sorted_items_ids_inner(&unit.slot);\n            }\n            _ => {}\n        }\n    }\n\n    pub fn select_item(&mut self, app: &mut Application, id: Option<WidgetId>) -> bool {\n        if self.locked_widget.is_some() || self.selected_chain.last() == id.as_ref() {\n            return false;\n        }\n        if let Some(id) = &id\n            && self.containers.contains_key(id)\n        {\n            app.send_message(id, NavSignal::Select(id.to_owned().into()));\n        }\n        match (self.selected_chain.is_empty(), id) {\n            (false, None) => {\n                for id in std::mem::take(&mut self.selected_chain).iter().rev() {\n                    app.send_message(id, NavSignal::Unselect);\n                }\n            }\n            (false, Some(mut id)) => {\n                if self.unfocus_when_selection_change {\n                    self.focus_text_input(app, None);\n                }\n                let mut chain = Vec::with_capacity(self.selected_chain.len());\n                while let Some(owner) = self.items_owners.get(&id) {\n                    if !chain.contains(&id) {\n                        chain.push(id.to_owned());\n                    }\n                    if !chain.contains(owner) {\n                        chain.push(owner.to_owned());\n                    }\n                    id = owner.to_owned();\n                }\n                chain.reverse();\n                let mut index = 0;\n                for (a, b) in self.selected_chain.iter().zip(chain.iter()) {\n                    if a != b {\n                        break;\n                    }\n                    index += 1;\n                }\n                for id in &self.selected_chain[index..] {\n                    app.send_message(id, NavSignal::Unselect);\n                }\n                for id in &chain[index..] {\n                    app.send_message(id, NavSignal::Select(().into()));\n                }\n                self.selected_chain = chain;\n            }\n            (true, Some(mut id)) => {\n                if self.unfocus_when_selection_change {\n                    self.focus_text_input(app, None);\n                }\n                self.selected_chain.clear();\n                while let Some(owner) = self.items_owners.get(&id) {\n                    if !self.selected_chain.contains(&id) {\n                        self.selected_chain.push(id.to_owned());\n                    }\n                    if !self.selected_chain.contains(owner) {\n                        self.selected_chain.push(owner.to_owned());\n                    }\n                    id = owner.to_owned();\n                }\n                self.selected_chain.reverse();\n                for id in &self.selected_chain {\n                    app.send_message(id, NavSignal::Select(().into()));\n                }\n            }\n            (true, None) => {}\n        }\n        true\n    }\n\n    pub fn focus_text_input(&mut self, app: &mut Application, id: Option<WidgetId>) {\n        if self.focused_text_input == id {\n            return;\n        }\n        if let Some(focused) = &self.focused_text_input {\n            app.send_message(focused, NavSignal::FocusTextInput(().into()));\n        }\n        self.focused_text_input = None;\n        if let Some(id) = id\n            && self.text_inputs.contains(&id)\n        {\n            app.send_message(&id, NavSignal::FocusTextInput(id.to_owned().into()));\n            self.focused_text_input = Some(id);\n        }\n    }\n\n    pub fn send_to_selected_item<T>(&self, app: &mut Application, data: T) -> bool\n    where\n        T: 'static + MessageData,\n    {\n        if let Some(id) = self.selected_item() {\n            app.send_message(id, data);\n            return true;\n        }\n        false\n    }\n\n    pub fn send_to_selected_container<T>(&self, app: &mut Application, data: T) -> bool\n    where\n        T: 'static + MessageData,\n    {\n        if let Some(id) = self.selected_container() {\n            app.send_message(id, data);\n            return true;\n        }\n        false\n    }\n\n    pub fn send_to_selected_button<T>(&self, app: &mut Application, data: T) -> bool\n    where\n        T: 'static + MessageData,\n    {\n        if let Some(id) = self.selected_button() {\n            app.send_message(id, data);\n            return true;\n        }\n        false\n    }\n\n    pub fn send_to_focused_text_input<T>(&self, app: &mut Application, data: T) -> bool\n    where\n        T: 'static + MessageData,\n    {\n        if let Some(id) = self.focused_text_input() {\n            app.send_message(id, data);\n            return true;\n        }\n        false\n    }\n\n    fn find_scroll_view_content(&self, id: &WidgetId) -> Option<WidgetId> {\n        if self.scroll_views.contains(id)\n            && let Some(items) = self.containers.get(id)\n        {\n            for item in items {\n                if self.scroll_view_contents.contains(item) {\n                    return Some(item.to_owned());\n                }\n            }\n        }\n        None\n    }\n\n    fn get_item_point(app: &Application, id: &WidgetId) -> Option<Vec2> {\n        if let Some(layout) = app.layout_data().items.get(id) {\n            let x = (layout.ui_space.left + layout.ui_space.right) * 0.5;\n            let y = (layout.ui_space.top + layout.ui_space.bottom) * 0.5;\n            Some(Vec2 { x, y })\n        } else {\n            None\n        }\n    }\n\n    fn get_selected_item_point(&self, app: &Application) -> Option<Vec2> {\n        Self::get_item_point(app, self.selected_item()?)\n    }\n\n    fn get_closest_item_point(app: &Application, id: &WidgetId, mut point: Vec2) -> Option<Vec2> {\n        if let Some(layout) = app.layout_data().items.get(id) {\n            point.x = point.x.max(layout.ui_space.left).min(layout.ui_space.right);\n            point.y = point.y.max(layout.ui_space.top).min(layout.ui_space.bottom);\n            Some(point)\n        } else {\n            None\n        }\n    }\n\n    fn find_item_closest_to_point(\n        app: &Application,\n        point: Vec2,\n        items: &HashSet<WidgetId>,\n    ) -> Option<WidgetId> {\n        items\n            .iter()\n            .filter_map(|id| {\n                Self::get_closest_item_point(app, id, point).map(|p| {\n                    let dx = p.x - point.x;\n                    let dy = p.y - point.y;\n                    (id, dx * dx + dy * dy)\n                })\n            })\n            .min_by(|a, b| a.1.partial_cmp(&b.1).unwrap())\n            .map(|m| m.0.to_owned())\n    }\n\n    fn find_item_closest_to_direction(\n        app: &Application,\n        point: Vec2,\n        direction: NavDirection,\n        items: &HashSet<WidgetId>,\n    ) -> Option<WidgetId> {\n        let dir = match direction {\n            NavDirection::Up => Vec2 { x: 0.0, y: -1.0 },\n            NavDirection::Down => Vec2 { x: 0.0, y: 1.0 },\n            NavDirection::Left => Vec2 { x: -1.0, y: 0.0 },\n            NavDirection::Right => Vec2 { x: 1.0, y: 0.0 },\n            _ => return None,\n        };\n        items\n            .iter()\n            .filter_map(|id| {\n                Self::get_closest_item_point(app, id, point).map(|p| {\n                    let dx = p.x - point.x;\n                    let dy = p.y - point.y;\n                    let len = (dx * dx + dy * dy).sqrt();\n                    let dot = dx / len * dir.x + dy / len * dir.y;\n                    let f = if len > 0.0 { dot / len } else { 0.0 };\n                    (id, f)\n                })\n            })\n            .filter(|m| m.1 > 1.0e-6)\n            .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap())\n            .map(|m| m.0.to_owned())\n    }\n\n    fn find_first_item(&self, items: &HashSet<WidgetId>) -> Option<WidgetId> {\n        self.sorted_items_ids\n            .iter()\n            .find(|id| items.contains(id))\n            .cloned()\n    }\n\n    fn find_last_item(&self, items: &HashSet<WidgetId>) -> Option<WidgetId> {\n        self.sorted_items_ids\n            .iter()\n            .rev()\n            .find(|id| items.contains(id))\n            .cloned()\n    }\n\n    fn find_prev_item(&self, id: &WidgetId, items: &HashSet<WidgetId>) -> Option<WidgetId> {\n        let mut found = false;\n        self.sorted_items_ids\n            .iter()\n            .rev()\n            .find(|i| {\n                if found {\n                    if items.contains(i) {\n                        return true;\n                    }\n                } else if i == &id {\n                    found = true;\n                }\n                false\n            })\n            .cloned()\n    }\n\n    fn find_next_item(&self, id: &WidgetId, items: &HashSet<WidgetId>) -> Option<WidgetId> {\n        let mut found = false;\n        self.sorted_items_ids\n            .iter()\n            .find(|i| {\n                if found {\n                    if items.contains(i) {\n                        return true;\n                    }\n                } else if i == &id {\n                    found = true;\n                }\n                false\n            })\n            .cloned()\n    }\n\n    // TODO: refactor this shit! my eyes are bleeding, like really dude ffs..\n    fn jump(&mut self, app: &mut Application, id: &WidgetId, data: NavJump) {\n        if let Some(items) = self.containers.get(id) {\n            match data {\n                NavJump::First => {\n                    if let Some(id) = self.find_first_item(items) {\n                        self.select_item(app, Some(id));\n                    }\n                }\n                NavJump::Last => {\n                    if let Some(id) = self.find_last_item(items) {\n                        self.select_item(app, Some(id));\n                    }\n                }\n                NavJump::TopLeft => {\n                    if let Some(layout) = app.layout_data().items.get(id) {\n                        let point = Vec2 {\n                            x: layout.ui_space.left,\n                            y: layout.ui_space.top,\n                        };\n                        if let Some(id) = Self::find_item_closest_to_point(app, point, items) {\n                            self.select_item(app, Some(id));\n                        }\n                    }\n                }\n                NavJump::TopRight => {\n                    if let Some(layout) = app.layout_data().items.get(id) {\n                        let point = Vec2 {\n                            x: layout.ui_space.right,\n                            y: layout.ui_space.top,\n                        };\n                        if let Some(id) = Self::find_item_closest_to_point(app, point, items) {\n                            self.select_item(app, Some(id));\n                        }\n                    }\n                }\n                NavJump::BottomLeft => {\n                    if let Some(layout) = app.layout_data().items.get(id) {\n                        let point = Vec2 {\n                            x: layout.ui_space.left,\n                            y: layout.ui_space.bottom,\n                        };\n                        if let Some(id) = Self::find_item_closest_to_point(app, point, items) {\n                            self.select_item(app, Some(id));\n                        }\n                    }\n                }\n                NavJump::BottomRight => {\n                    if let Some(layout) = app.layout_data().items.get(id) {\n                        let point = Vec2 {\n                            x: layout.ui_space.right,\n                            y: layout.ui_space.bottom,\n                        };\n                        if let Some(id) = Self::find_item_closest_to_point(app, point, items) {\n                            self.select_item(app, Some(id));\n                        }\n                    }\n                }\n                NavJump::MiddleCenter => {\n                    if let Some(layout) = app.layout_data().items.get(id) {\n                        let point = Vec2 {\n                            x: (layout.ui_space.left + layout.ui_space.right) * 0.5,\n                            y: (layout.ui_space.top + layout.ui_space.bottom) * 0.5,\n                        };\n                        if let Some(id) = Self::find_item_closest_to_point(app, point, items) {\n                            self.select_item(app, Some(id));\n                        }\n                    }\n                }\n                NavJump::Loop(direction) => match direction {\n                    NavDirection::Up\n                    | NavDirection::Down\n                    | NavDirection::Left\n                    | NavDirection::Right => {\n                        if let Some(point) = self.get_selected_item_point(app) {\n                            if let Some(id) =\n                                Self::find_item_closest_to_direction(app, point, direction, items)\n                            {\n                                self.select_item(app, Some(id));\n                            } else if let Some(id) = self.items_owners.get(id) {\n                                match direction {\n                                    NavDirection::Up => app.send_message(id, NavSignal::Up),\n                                    NavDirection::Down => app.send_message(id, NavSignal::Down),\n                                    NavDirection::Left => app.send_message(id, NavSignal::Left),\n                                    NavDirection::Right => app.send_message(id, NavSignal::Right),\n                                    _ => {}\n                                }\n                            }\n                        }\n                    }\n                    NavDirection::Prev => {\n                        if let Some(id) = self.selected_chain.last() {\n                            if let Some(id) = self.find_prev_item(id, items) {\n                                self.select_item(app, Some(id));\n                            } else if let Some(id) = self.find_last_item(items) {\n                                self.select_item(app, Some(id));\n                            } else if let Some(id) = self.items_owners.get(id) {\n                                app.send_message(id, NavSignal::Prev);\n                            }\n                        }\n                    }\n                    NavDirection::Next => {\n                        if let Some(id) = self.selected_chain.last() {\n                            if let Some(id) = self.find_next_item(id, items) {\n                                self.select_item(app, Some(id));\n                            } else if let Some(id) = self.find_first_item(items) {\n                                self.select_item(app, Some(id));\n                            } else if let Some(id) = self.items_owners.get(id) {\n                                app.send_message(id, NavSignal::Next);\n                            }\n                        }\n                    }\n                    _ => {}\n                },\n                NavJump::Escape(direction, idref) => match direction {\n                    NavDirection::Up\n                    | NavDirection::Down\n                    | NavDirection::Left\n                    | NavDirection::Right => {\n                        if let Some(point) = self.get_selected_item_point(app) {\n                            if let Some(id) =\n                                Self::find_item_closest_to_direction(app, point, direction, items)\n                            {\n                                self.select_item(app, Some(id));\n                            } else if let Some(id) = idref.read() {\n                                self.select_item(app, Some(id));\n                            } else if let Some(id) = self.items_owners.get(id) {\n                                match direction {\n                                    NavDirection::Up => app.send_message(id, NavSignal::Up),\n                                    NavDirection::Down => app.send_message(id, NavSignal::Down),\n                                    NavDirection::Left => app.send_message(id, NavSignal::Left),\n                                    NavDirection::Right => app.send_message(id, NavSignal::Right),\n                                    _ => {}\n                                }\n                            }\n                        }\n                    }\n                    NavDirection::Prev => {\n                        if let Some(id) = self.selected_chain.last() {\n                            if let Some(id) = self.find_prev_item(id, items) {\n                                self.select_item(app, Some(id));\n                            } else if let Some(id) = idref.read() {\n                                self.select_item(app, Some(id));\n                            } else if let Some(id) = self.items_owners.get(id) {\n                                app.send_message(id, NavSignal::Prev);\n                            }\n                        }\n                    }\n                    NavDirection::Next => {\n                        if let Some(id) = self.selected_chain.last() {\n                            if let Some(id) = self.find_next_item(id, items) {\n                                self.select_item(app, Some(id));\n                            } else if let Some(id) = idref.read() {\n                                self.select_item(app, Some(id));\n                            } else if let Some(id) = self.items_owners.get(id) {\n                                app.send_message(id, NavSignal::Next);\n                            }\n                        }\n                    }\n                    _ => {}\n                },\n                NavJump::Scroll(scroll) => {\n                    fn factor(\n                        this: &DefaultInteractionsEngine,\n                        app: &mut Application,\n                        id: &WidgetId,\n                        v: Vec2,\n                        relative: bool,\n                    ) {\n                        if let Some(oid) = this.find_scroll_view_content(id) {\n                            let a = app.layout_data().find_or_ui_space(oid.path());\n                            let b = app.layout_data().find_or_ui_space(id.path());\n                            let asize = a.local_space.size();\n                            let bsize = b.local_space.size();\n                            let f = Vec2 {\n                                x: if bsize.x > 0.0 {\n                                    asize.x / bsize.x\n                                } else {\n                                    0.0\n                                },\n                                y: if bsize.y > 0.0 {\n                                    asize.y / bsize.y\n                                } else {\n                                    0.0\n                                },\n                            };\n                            app.send_message(\n                                id,\n                                NavSignal::Jump(NavJump::Scroll(NavScroll::Change(v, f, relative))),\n                            );\n                        }\n                    }\n\n                    fn units(\n                        this: &DefaultInteractionsEngine,\n                        app: &mut Application,\n                        id: &WidgetId,\n                        v: Vec2,\n                        relative: bool,\n                    ) {\n                        if let Some(oid) = this.find_scroll_view_content(id) {\n                            let a = app.layout_data().find_or_ui_space(oid.path());\n                            let b = app.layout_data().find_or_ui_space(id.path());\n                            let asize = a.local_space.size();\n                            let bsize = b.local_space.size();\n                            let dsize = Vec2 {\n                                x: asize.x - bsize.x,\n                                y: asize.y - bsize.y,\n                            };\n                            let v = Vec2 {\n                                x: if dsize.x > 0.0 { v.x / dsize.x } else { 0.0 },\n                                y: if dsize.y > 0.0 { v.y / dsize.y } else { 0.0 },\n                            };\n                            let f = Vec2 {\n                                x: if bsize.x > 0.0 {\n                                    asize.x / bsize.x\n                                } else {\n                                    0.0\n                                },\n                                y: if bsize.y > 0.0 {\n                                    asize.y / bsize.y\n                                } else {\n                                    0.0\n                                },\n                            };\n                            app.send_message(\n                                id,\n                                NavSignal::Jump(NavJump::Scroll(NavScroll::Change(v, f, relative))),\n                            );\n                        }\n                    }\n\n                    match scroll {\n                        NavScroll::Factor(v, relative) => factor(self, app, id, v, relative),\n                        NavScroll::DirectFactor(idref, v, relative) => {\n                            if let Some(id) = idref.read() {\n                                factor(self, app, &id, v, relative);\n                            }\n                        }\n                        NavScroll::Units(v, relative) => units(self, app, id, v, relative),\n                        NavScroll::DirectUnits(idref, v, relative) => {\n                            if let Some(id) = idref.read() {\n                                units(self, app, &id, v, relative);\n                            }\n                        }\n                        NavScroll::Widget(idref, anchor) => {\n                            if let (Some(wid), Some(oid)) =\n                                (idref.read(), self.find_scroll_view_content(id))\n                                && let Some(rect) = app.layout_data().rect_relative_to(&wid, &oid)\n                            {\n                                let aitem = app.layout_data().find_or_ui_space(oid.path());\n                                let bitem = app.layout_data().find_or_ui_space(id.path());\n                                let x = lerp(rect.left, rect.right, anchor.x);\n                                let y = lerp(rect.top, rect.bottom, anchor.y);\n                                let asize = aitem.local_space.size();\n                                let bsize = bitem.local_space.size();\n                                let v = Vec2 {\n                                    x: if asize.x > 0.0 { x / asize.x } else { 0.0 },\n                                    y: if asize.y > 0.0 { y / asize.y } else { 0.0 },\n                                };\n                                let f = Vec2 {\n                                    x: if bsize.x > 0.0 {\n                                        asize.x / bsize.x\n                                    } else {\n                                        0.0\n                                    },\n                                    y: if bsize.y > 0.0 {\n                                        asize.y / bsize.y\n                                    } else {\n                                        0.0\n                                    },\n                                };\n                                app.send_message(\n                                    id,\n                                    NavSignal::Jump(NavJump::Scroll(NavScroll::Change(\n                                        v, f, false,\n                                    ))),\n                                );\n                            }\n                        }\n                        _ => {}\n                    }\n                }\n            }\n        }\n    }\n\n    pub fn find_button(&self, app: &Application, x: Scalar, y: Scalar) -> Option<(WidgetId, Vec2)> {\n        self.find_button_inner(app, x, y, app.rendered_tree(), app.layout_data().ui_space)\n    }\n\n    fn find_button_inner(\n        &self,\n        app: &Application,\n        x: Scalar,\n        y: Scalar,\n        unit: &WidgetUnit,\n        mut clip: Rect,\n    ) -> Option<(WidgetId, Vec2)> {\n        if x < clip.left || x > clip.right || y < clip.top || y > clip.bottom {\n            return None;\n        }\n        let mut result = None;\n        if let Some(data) = unit.as_data()\n            && self.buttons.contains(data.id())\n            && let Some(layout) = app.layout_data().items.get(data.id())\n        {\n            let rect = layout.ui_space;\n            if x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom {\n                let size = rect.size();\n                let pos = Vec2 {\n                    x: if size.x > 0.0 {\n                        (x - rect.left) / size.x\n                    } else {\n                        0.0\n                    },\n                    y: if size.y > 0.0 {\n                        (y - rect.top) / size.y\n                    } else {\n                        0.0\n                    },\n                };\n                result = Some((data.id().to_owned(), pos));\n            }\n        }\n        match unit {\n            WidgetUnit::AreaBox(unit) => {\n                if let Some(id) = self.find_button_inner(app, x, y, &unit.slot, clip) {\n                    result = Some(id);\n                }\n            }\n            WidgetUnit::ContentBox(unit) => {\n                if unit.clipping\n                    && let Some(item) = app.layout_data().items.get(&unit.id)\n                {\n                    clip = item.ui_space;\n                }\n                for item in &unit.items {\n                    if let Some(id) = self.find_button_inner(app, x, y, &item.slot, clip) {\n                        result = Some(id);\n                    }\n                }\n            }\n            WidgetUnit::FlexBox(unit) => {\n                for item in &unit.items {\n                    if let Some(id) = self.find_button_inner(app, x, y, &item.slot, clip) {\n                        result = Some(id);\n                    }\n                }\n            }\n            WidgetUnit::GridBox(unit) => {\n                for item in &unit.items {\n                    if let Some(id) = self.find_button_inner(app, x, y, &item.slot, clip) {\n                        result = Some(id);\n                    }\n                }\n            }\n            WidgetUnit::SizeBox(unit) => {\n                if let Some(id) = self.find_button_inner(app, x, y, &unit.slot, clip) {\n                    result = Some(id);\n                }\n            }\n            _ => {}\n        }\n        result\n    }\n\n    pub fn does_hover_widget(&self, app: &Application, x: Scalar, y: Scalar) -> bool {\n        Self::does_hover_widget_inner(app, x, y, app.rendered_tree())\n    }\n\n    fn does_hover_widget_inner(app: &Application, x: Scalar, y: Scalar, unit: &WidgetUnit) -> bool {\n        if let Some(data) = unit.as_data()\n            && let Some(layout) = app.layout_data().items.get(data.id())\n        {\n            let rect = layout.ui_space;\n            if x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom {\n                return true;\n            }\n        }\n        match unit {\n            WidgetUnit::AreaBox(unit) => {\n                if Self::does_hover_widget_inner(app, x, y, &unit.slot) {\n                    return true;\n                }\n            }\n            WidgetUnit::ContentBox(unit) => {\n                for item in &unit.items {\n                    if Self::does_hover_widget_inner(app, x, y, &item.slot) {\n                        return true;\n                    }\n                }\n            }\n            WidgetUnit::FlexBox(unit) => {\n                for item in &unit.items {\n                    if Self::does_hover_widget_inner(app, x, y, &item.slot) {\n                        return true;\n                    }\n                }\n            }\n            WidgetUnit::GridBox(unit) => {\n                for item in &unit.items {\n                    if Self::does_hover_widget_inner(app, x, y, &item.slot) {\n                        return true;\n                    }\n                }\n            }\n            WidgetUnit::SizeBox(unit) => {\n                if Self::does_hover_widget_inner(app, x, y, &unit.slot) {\n                    return true;\n                }\n            }\n            _ => {}\n        }\n        false\n    }\n}\n\nimpl InteractionsEngine<DefaultInteractionsEngineResult, ()> for DefaultInteractionsEngine {\n    fn perform_interactions(\n        &mut self,\n        app: &mut Application,\n    ) -> Result<DefaultInteractionsEngineResult, ()> {\n        let mut to_resize = HashSet::new();\n        let mut to_relative_layout = HashSet::new();\n        let mut to_select = None;\n        let mut to_jump = HashMap::new();\n        let mut to_focus = None;\n        let mut to_send_axis = vec![];\n        let mut to_send_custom = vec![];\n        for (id, signal) in app.signals() {\n            if let Some(signal) = signal.as_any().downcast_ref() {\n                match signal {\n                    ResizeListenerSignal::Register => {\n                        if let Some(item) = app.layout_data().items.get(id) {\n                            self.resize_listeners\n                                .insert(id.to_owned(), item.local_space.size());\n                            to_resize.insert(id.to_owned());\n                        }\n                    }\n                    ResizeListenerSignal::Unregister => {\n                        self.resize_listeners.remove(id);\n                    }\n                    _ => {}\n                }\n            } else if let Some(signal) = signal.as_any().downcast_ref() {\n                match signal {\n                    RelativeLayoutListenerSignal::Register(relative_to) => {\n                        if let (Some(item), Some(rect)) = (\n                            app.layout_data().items.get(relative_to),\n                            app.layout_data().rect_relative_to(id, relative_to),\n                        ) {\n                            self.relative_layout_listeners.insert(\n                                id.to_owned(),\n                                (relative_to.to_owned(), item.local_space.size(), rect),\n                            );\n                            to_relative_layout.insert(id.to_owned());\n                        }\n                    }\n                    RelativeLayoutListenerSignal::Unregister => {\n                        self.relative_layout_listeners.remove(id);\n                    }\n                    _ => {}\n                }\n            } else if let Some(signal) = signal.as_any().downcast_ref() {\n                match signal {\n                    NavSignal::Register(t) => match t {\n                        NavType::Container => {\n                            self.containers.insert(id.to_owned(), Default::default());\n                        }\n                        NavType::Item => {\n                            if let Some((key, items)) = self\n                                .containers\n                                .iter_mut()\n                                .filter(|(k, _)| {\n                                    k.path() != id.path() && id.path().starts_with(k.path())\n                                })\n                                .max_by(|(a, _), (b, _)| a.depth().cmp(&b.depth()))\n                            {\n                                items.remove(id);\n                                items.insert(id.to_owned());\n                                self.items_owners.insert(id.to_owned(), key.to_owned());\n                            }\n                        }\n                        NavType::Button => {\n                            self.buttons.insert(id.to_owned());\n                        }\n                        NavType::TextInput => {\n                            self.text_inputs.insert(id.to_owned());\n                        }\n                        NavType::ScrollView => {\n                            self.scroll_views.insert(id.to_owned());\n                        }\n                        NavType::ScrollViewContent => {\n                            self.scroll_view_contents.insert(id.to_owned());\n                        }\n                        NavType::Tracking(who) => {\n                            if let Some(who) = who.read() {\n                                self.tracking.insert(id.to_owned(), who);\n                            }\n                        }\n                    },\n                    NavSignal::Unregister(t) => match t {\n                        NavType::Container => {\n                            if let Some(items) = self.containers.remove(id) {\n                                for id in items {\n                                    self.items_owners.remove(&id);\n                                }\n                            }\n                        }\n                        NavType::Item => {\n                            if let Some(key) = self.items_owners.remove(id)\n                                && let Some(items) = self.containers.get_mut(&key)\n                            {\n                                items.remove(&key);\n                            }\n                            if let Some(lid) = &self.locked_widget\n                                && lid == id\n                            {\n                                self.locked_widget = None;\n                            }\n                        }\n                        NavType::Button => {\n                            self.buttons.remove(id);\n                        }\n                        NavType::TextInput => {\n                            self.text_inputs.remove(id);\n                            if let Some(focused) = &self.focused_text_input\n                                && focused == id\n                            {\n                                self.focused_text_input = None;\n                            }\n                        }\n                        NavType::ScrollView => {\n                            self.scroll_views.remove(id);\n                        }\n                        NavType::ScrollViewContent => {\n                            self.scroll_view_contents.remove(id);\n                        }\n                        NavType::Tracking(_) => {\n                            self.tracking.remove(id);\n                        }\n                    },\n                    NavSignal::Select(idref) => to_select = Some(idref.to_owned()),\n                    NavSignal::Unselect => to_select = Some(().into()),\n                    NavSignal::Lock => {\n                        if self.locked_widget.is_none() {\n                            self.locked_widget = Some(id.to_owned());\n                        }\n                    }\n                    NavSignal::Unlock => {\n                        if let Some(lid) = &self.locked_widget\n                            && lid == id\n                        {\n                            self.locked_widget = None;\n                        }\n                    }\n                    NavSignal::Jump(data) => {\n                        to_jump.insert(id.to_owned(), data.to_owned());\n                    }\n                    NavSignal::FocusTextInput(idref) => to_focus = Some(idref.to_owned()),\n                    NavSignal::Axis(name, value) => to_send_axis.push((name.to_owned(), *value)),\n                    NavSignal::Custom(idref, data) => {\n                        to_send_custom.push((idref.to_owned(), data.to_owned()))\n                    }\n                    _ => {}\n                }\n            }\n        }\n\n        for (k, v) in &mut self.resize_listeners {\n            if let Some(item) = app.layout_data().items.get(k) {\n                let size = item.local_space.size();\n                if to_resize.contains(k)\n                    || (v.x - size.x).abs() >= 1.0e-6\n                    || (v.y - size.y).abs() >= 1.0e-6\n                {\n                    app.send_message(k, ResizeListenerSignal::Change(size));\n                    *v = size;\n                }\n            }\n        }\n        for (k, (r, s, v)) in &mut self.relative_layout_listeners {\n            if let (Some(item), Some(rect)) = (\n                app.layout_data().items.get(r),\n                app.layout_data().rect_relative_to(k, r),\n            ) {\n                let size = item.local_space.size();\n                if to_relative_layout.contains(k)\n                    || (s.x - size.x).abs() >= 1.0e-6\n                    || (s.y - size.y).abs() >= 1.0e-6\n                    || (v.left - rect.left).abs() >= 1.0e-6\n                    || (v.right - rect.right).abs() >= 1.0e-6\n                    || (v.top - rect.top).abs() >= 1.0e-6\n                    || (v.bottom - rect.bottom).abs() >= 1.0e-6\n                {\n                    app.send_message(k, RelativeLayoutListenerSignal::Change(size, rect));\n                    *s = size;\n                    *v = rect;\n                }\n            }\n        }\n        if !to_jump.is_empty() {\n            self.cache_sorted_items_ids(app);\n        }\n        if let Some(idref) = to_select {\n            self.select_item(app, idref.read());\n        }\n        for (id, data) in to_jump {\n            self.jump(app, &id, data);\n        }\n        if let Some(idref) = to_focus {\n            self.focus_text_input(app, idref.read());\n        }\n        for (name, value) in to_send_axis {\n            self.send_to_selected_item(app, NavSignal::Axis(name, value));\n        }\n        for (idref, data) in to_send_custom {\n            if let Some(id) = idref.read() {\n                app.send_message(&id, NavSignal::Custom(().into(), data));\n            } else {\n                self.send_to_selected_item(app, NavSignal::Custom(().into(), data));\n            }\n        }\n        let mut result = DefaultInteractionsEngineResult::default();\n        while let Some(interaction) = self.interactions_queue.pop_front() {\n            match interaction {\n                Interaction::None => {}\n                Interaction::Navigate(msg) => match msg {\n                    NavSignal::Select(idref) => {\n                        self.select_item(app, idref.read());\n                    }\n                    NavSignal::Unselect => {\n                        self.select_item(app, None);\n                    }\n                    NavSignal::Accept(_) | NavSignal::Context(_) | NavSignal::Cancel(_) => {\n                        self.send_to_selected_item(app, msg);\n                    }\n                    NavSignal::Up\n                    | NavSignal::Down\n                    | NavSignal::Left\n                    | NavSignal::Right\n                    | NavSignal::Prev\n                    | NavSignal::Next => {\n                        self.send_to_selected_container(app, msg);\n                    }\n                    NavSignal::FocusTextInput(idref) => {\n                        self.focus_text_input(app, idref.read());\n                    }\n                    NavSignal::TextChange(_) => {\n                        if self.send_to_focused_text_input(app, msg) {\n                            result.captured_text_change = true;\n                        }\n                    }\n                    NavSignal::Custom(idref, data) => {\n                        if let Some(id) = idref.read() {\n                            app.send_message(&id, NavSignal::Custom(().into(), data));\n                        } else {\n                            self.send_to_selected_item(app, NavSignal::Custom(().into(), data));\n                        }\n                    }\n                    NavSignal::Jump(jump) => match jump {\n                        NavJump::Scroll(NavScroll::Factor(_, _))\n                        | NavJump::Scroll(NavScroll::Units(_, _))\n                        | NavJump::Scroll(NavScroll::Widget(_, _)) => {\n                            if let Some(id) = self.selected_scroll_view().cloned() {\n                                self.jump(app, &id, jump);\n                            }\n                        }\n                        _ => {}\n                    },\n                    _ => {}\n                },\n                Interaction::PointerMove(Vec2 { x, y }) => {\n                    if self.locked_widget.is_some() {\n                        if self.selected_button().is_some() {\n                            result.captured_pointer_location = true;\n                        }\n                    } else if let Some((found, _)) = self.find_button(app, x, y) {\n                        result.captured_pointer_location = true;\n                        self.select_item(app, Some(found));\n                    } else {\n                        if self.deselect_when_no_button_found {\n                            self.select_item(app, None);\n                        }\n                        if self.does_hover_widget(app, x, y) {\n                            result.captured_pointer_location = true;\n                        }\n                    }\n                    for (id, who) in &self.tracking {\n                        if let Some(layout) = app.layout_data().items.get(who) {\n                            let rect = layout.ui_space;\n                            let size = rect.size();\n                            app.send_message(\n                                id,\n                                NavSignal::Axis(\n                                    \"pointer-x\".to_owned(),\n                                    if size.x > 0.0 {\n                                        (x - rect.left) / size.x\n                                    } else {\n                                        0.0\n                                    },\n                                ),\n                            );\n                            app.send_message(\n                                id,\n                                NavSignal::Axis(\n                                    \"pointer-y\".to_owned(),\n                                    if size.y > 0.0 {\n                                        (y - rect.top) / size.y\n                                    } else {\n                                        0.0\n                                    },\n                                ),\n                            );\n                            app.send_message(\n                                id,\n                                NavSignal::Axis(\"pointer-x-unscaled\".to_owned(), x - rect.left),\n                            );\n                            app.send_message(\n                                id,\n                                NavSignal::Axis(\"pointer-y-unscaled\".to_owned(), y - rect.top),\n                            );\n                            app.send_message(id, NavSignal::Axis(\"pointer-x-ui\".to_owned(), x));\n                            app.send_message(id, NavSignal::Axis(\"pointer-y-ui\".to_owned(), y));\n                            result.captured_pointer_location = true;\n                            result.captured_pointer_action = true;\n                        }\n                    }\n                }\n                Interaction::PointerDown(button, Vec2 { x, y }) => {\n                    if let Some((found, _)) = self.find_button(app, x, y) {\n                        self.select_item(app, Some(found));\n                        result.captured_pointer_location = true;\n                        let action = match button {\n                            PointerButton::Trigger => NavSignal::Accept(true),\n                            PointerButton::Context => NavSignal::Context(true),\n                        };\n                        if self.send_to_selected_button(app, action) {\n                            result.captured_pointer_action = true;\n                        }\n                    } else {\n                        if self.deselect_when_no_button_found {\n                            self.select_item(app, None);\n                        }\n                        if self.does_hover_widget(app, x, y) {\n                            result.captured_pointer_location = true;\n                        }\n                    }\n                    for (id, who) in &self.tracking {\n                        if let Some(layout) = app.layout_data().items.get(who) {\n                            let rect = layout.ui_space;\n                            let size = rect.size();\n                            app.send_message(\n                                id,\n                                NavSignal::Axis(\n                                    \"pointer-x\".to_owned(),\n                                    if size.x > 0.0 {\n                                        (x - rect.left) / size.x\n                                    } else {\n                                        0.0\n                                    },\n                                ),\n                            );\n                            app.send_message(\n                                id,\n                                NavSignal::Axis(\n                                    \"pointer-y\".to_owned(),\n                                    if size.y > 0.0 {\n                                        (y - rect.top) / size.y\n                                    } else {\n                                        0.0\n                                    },\n                                ),\n                            );\n                            app.send_message(\n                                id,\n                                NavSignal::Axis(\"pointer-x-unscaled\".to_owned(), x - rect.left),\n                            );\n                            app.send_message(\n                                id,\n                                NavSignal::Axis(\"pointer-y-unscaled\".to_owned(), y - rect.top),\n                            );\n                            app.send_message(id, NavSignal::Axis(\"pointer-x-ui\".to_owned(), x));\n                            app.send_message(id, NavSignal::Axis(\"pointer-y-ui\".to_owned(), y));\n                            result.captured_pointer_location = true;\n                            result.captured_pointer_action = true;\n                        }\n                    }\n                }\n                Interaction::PointerUp(button, Vec2 { x, y }) => {\n                    let action = match button {\n                        PointerButton::Trigger => NavSignal::Accept(false),\n                        PointerButton::Context => NavSignal::Context(false),\n                    };\n                    if self.send_to_selected_button(app, action) {\n                        result.captured_pointer_action = true;\n                    }\n                    for (id, who) in &self.tracking {\n                        if let Some(layout) = app.layout_data().items.get(who) {\n                            let rect = layout.ui_space;\n                            let size = rect.size();\n                            app.send_message(\n                                id,\n                                NavSignal::Axis(\n                                    \"pointer-x\".to_owned(),\n                                    if size.x > 0.0 {\n                                        (x - rect.left) / size.x\n                                    } else {\n                                        0.0\n                                    },\n                                ),\n                            );\n                            app.send_message(\n                                id,\n                                NavSignal::Axis(\n                                    \"pointer-y\".to_owned(),\n                                    if size.y > 0.0 {\n                                        (y - rect.top) / size.y\n                                    } else {\n                                        0.0\n                                    },\n                                ),\n                            );\n                            app.send_message(\n                                id,\n                                NavSignal::Axis(\"pointer-x-unscaled\".to_owned(), x - rect.left),\n                            );\n                            app.send_message(\n                                id,\n                                NavSignal::Axis(\"pointer-y-unscaled\".to_owned(), y - rect.top),\n                            );\n                            app.send_message(id, NavSignal::Axis(\"pointer-x-ui\".to_owned(), x));\n                            app.send_message(id, NavSignal::Axis(\"pointer-y-ui\".to_owned(), y));\n                            result.captured_pointer_location = true;\n                            result.captured_pointer_action = true;\n                        }\n                    }\n                }\n            }\n        }\n        Ok(result)\n    }\n}\n"
  },
  {
    "path": "crates/core/src/interactive/mod.rs",
    "content": "//! Interactivity traits\n\npub mod default_interactions_engine;\n\nuse crate::application::Application;\n\npub trait InteractionsEngine<R, E> {\n    fn perform_interactions(&mut self, app: &mut Application) -> Result<R, E>;\n}\n\nimpl InteractionsEngine<(), ()> for () {\n    fn perform_interactions(&mut self, _: &mut Application) -> Result<(), ()> {\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/core/src/layout/default_layout_engine.rs",
    "content": "use crate::{\n    Scalar,\n    layout::{CoordsMapping, Layout, LayoutEngine, LayoutItem, LayoutNode},\n    widget::{\n        WidgetId,\n        unit::{\n            WidgetUnit,\n            area::AreaBox,\n            content::ContentBox,\n            flex::FlexBox,\n            grid::GridBox,\n            image::{ImageBox, ImageBoxSizeValue},\n            size::{SizeBox, SizeBoxAspectRatio, SizeBoxSizeValue},\n            text::{TextBox, TextBoxSizeValue},\n        },\n        utils::{Rect, Vec2, lerp},\n    },\n};\nuse std::collections::HashMap;\n\npub trait TextMeasurementEngine {\n    fn measure_text(\n        &self,\n        size_available: Vec2,\n        mapping: &CoordsMapping,\n        widget: &TextBox,\n    ) -> Option<Rect>;\n}\n\nimpl TextMeasurementEngine for () {\n    fn measure_text(&self, _: Vec2, _: &CoordsMapping, _: &TextBox) -> Option<Rect> {\n        None\n    }\n}\n\npub struct DefaultLayoutEngine<TME: TextMeasurementEngine = ()> {\n    text_measurement_engine: TME,\n}\n\nimpl<TME: TextMeasurementEngine + Default> Default for DefaultLayoutEngine<TME> {\n    fn default() -> Self {\n        Self {\n            text_measurement_engine: TME::default(),\n        }\n    }\n}\n\nimpl<TME: TextMeasurementEngine + Clone> Clone for DefaultLayoutEngine<TME> {\n    fn clone(&self) -> Self {\n        Self {\n            text_measurement_engine: self.text_measurement_engine.clone(),\n        }\n    }\n}\n\nimpl<TME: TextMeasurementEngine + Copy> Copy for DefaultLayoutEngine<TME> {}\n\nimpl<TME: TextMeasurementEngine> DefaultLayoutEngine<TME> {\n    pub fn new(engine: TME) -> Self {\n        Self {\n            text_measurement_engine: engine,\n        }\n    }\n\n    pub fn layout_node(\n        &self,\n        size_available: Vec2,\n        mapping: &CoordsMapping,\n        unit: &WidgetUnit,\n    ) -> Option<LayoutNode> {\n        match unit {\n            WidgetUnit::None | WidgetUnit::PortalBox(_) => None,\n            WidgetUnit::AreaBox(b) => self.layout_area_box(size_available, mapping, b),\n            WidgetUnit::ContentBox(b) => self.layout_content_box(size_available, mapping, b),\n            WidgetUnit::FlexBox(b) => self.layout_flex_box(size_available, mapping, b),\n            WidgetUnit::GridBox(b) => self.layout_grid_box(size_available, mapping, b),\n            WidgetUnit::SizeBox(b) => self.layout_size_box(size_available, mapping, b),\n            WidgetUnit::ImageBox(b) => self.layout_image_box(size_available, b),\n            WidgetUnit::TextBox(b) => self.layout_text_box(size_available, mapping, b),\n        }\n    }\n\n    pub fn layout_area_box(\n        &self,\n        size_available: Vec2,\n        mapping: &CoordsMapping,\n        unit: &AreaBox,\n    ) -> Option<LayoutNode> {\n        if !unit.id.is_valid() {\n            return None;\n        }\n        let (children, w, h) =\n            if let Some(child) = self.layout_node(size_available, mapping, &unit.slot) {\n                let w = child.local_space.width();\n                let h = child.local_space.height();\n                (vec![child], w, h)\n            } else {\n                (vec![], 0.0, 0.0)\n            };\n        let local_space = Rect {\n            left: 0.0,\n            right: w,\n            top: 0.0,\n            bottom: h,\n        };\n        Some(LayoutNode {\n            id: unit.id.to_owned(),\n            local_space,\n            children,\n        })\n    }\n\n    pub fn layout_content_box(\n        &self,\n        size_available: Vec2,\n        mapping: &CoordsMapping,\n        unit: &ContentBox,\n    ) -> Option<LayoutNode> {\n        if !unit.id.is_valid() {\n            return None;\n        }\n        let children = unit\n            .items\n            .iter()\n            .filter_map(|item| {\n                let left = lerp(0.0, size_available.x, item.layout.anchors.left);\n                let left = left\n                    + item.layout.margin.left\n                    + item.layout.offset.x\n                    + unit.content_reposition.offset.x;\n                let left = left * unit.content_reposition.scale.x;\n                let right = lerp(0.0, size_available.x, item.layout.anchors.right);\n                let right = right - item.layout.margin.right\n                    + item.layout.offset.x\n                    + unit.content_reposition.offset.x;\n                let right = right * unit.content_reposition.scale.x;\n                let top = lerp(0.0, size_available.y, item.layout.anchors.top);\n                let top = top\n                    + item.layout.margin.top\n                    + item.layout.offset.y\n                    + unit.content_reposition.offset.y;\n                let top = top * unit.content_reposition.scale.y;\n                let bottom = lerp(0.0, size_available.y, item.layout.anchors.bottom);\n                let bottom = bottom - item.layout.margin.bottom\n                    + item.layout.offset.y\n                    + unit.content_reposition.offset.y;\n                let bottom = bottom * unit.content_reposition.scale.y;\n                let width = (right - left).max(0.0);\n                let height = (bottom - top).max(0.0);\n                let size = Vec2 {\n                    x: width,\n                    y: height,\n                };\n                if let Some(mut child) = self.layout_node(size, mapping, &item.slot) {\n                    let diff = child.local_space.width() - width;\n                    let ox = lerp(0.0, diff, item.layout.align.x);\n                    child.local_space.left += left - ox;\n                    child.local_space.right += left - ox;\n                    let diff = child.local_space.height() - height;\n                    let oy = lerp(0.0, diff, item.layout.align.y);\n                    child.local_space.top += top - oy;\n                    child.local_space.bottom += top - oy;\n                    let w = child.local_space.width().min(size_available.x);\n                    let h = child.local_space.height().min(size_available.y);\n                    if item.layout.keep_in_bounds.cut.left {\n                        child.local_space.left = child.local_space.left.max(0.0);\n                        if item.layout.keep_in_bounds.preserve.width {\n                            child.local_space.right = child.local_space.left + w;\n                        }\n                    }\n                    if item.layout.keep_in_bounds.cut.right {\n                        child.local_space.right = child.local_space.right.min(size_available.x);\n                        if item.layout.keep_in_bounds.preserve.width {\n                            child.local_space.left = child.local_space.right - w;\n                        }\n                    }\n                    if item.layout.keep_in_bounds.cut.top {\n                        child.local_space.top = child.local_space.top.max(0.0);\n                        if item.layout.keep_in_bounds.preserve.height {\n                            child.local_space.bottom = child.local_space.top + h;\n                        }\n                    }\n                    if item.layout.keep_in_bounds.cut.bottom {\n                        child.local_space.bottom = child.local_space.bottom.min(size_available.y);\n                        if item.layout.keep_in_bounds.preserve.height {\n                            child.local_space.top = child.local_space.bottom - h;\n                        }\n                    }\n                    Some(child)\n                } else {\n                    None\n                }\n            })\n            .collect::<Vec<_>>();\n        Some(LayoutNode {\n            id: unit.id.to_owned(),\n            local_space: Rect {\n                left: 0.0,\n                right: size_available.x,\n                top: 0.0,\n                bottom: size_available.y,\n            },\n            children,\n        })\n    }\n\n    pub fn layout_flex_box(\n        &self,\n        size_available: Vec2,\n        mapping: &CoordsMapping,\n        unit: &FlexBox,\n    ) -> Option<LayoutNode> {\n        if !unit.id.is_valid() {\n            return None;\n        }\n        if unit.wrap {\n            Some(self.layout_flex_box_wrapping(size_available, mapping, unit))\n        } else {\n            Some(self.layout_flex_box_no_wrap(size_available, mapping, unit))\n        }\n    }\n\n    pub fn layout_flex_box_wrapping(\n        &self,\n        size_available: Vec2,\n        mapping: &CoordsMapping,\n        unit: &FlexBox,\n    ) -> LayoutNode {\n        let main_available = if unit.direction.is_horizontal() {\n            size_available.x\n        } else {\n            size_available.y\n        };\n        let (lines, count) = {\n            let mut main = 0.0;\n            let mut cross: Scalar = 0.0;\n            let mut grow = 0.0;\n            let items = unit\n                .items\n                .iter()\n                .filter(|item| item.slot.is_some() && item.slot.as_data().unwrap().id().is_valid())\n                .collect::<Vec<_>>();\n            let count = items.len();\n            let mut lines = vec![];\n            let mut line = vec![];\n            for item in items {\n                let local_main = item.layout.basis.unwrap_or_else(|| {\n                    if unit.direction.is_horizontal() {\n                        self.calc_unit_min_width(size_available, mapping, &item.slot)\n                    } else {\n                        self.calc_unit_min_height(size_available, mapping, &item.slot)\n                    }\n                });\n                let local_main = local_main\n                    + if unit.direction.is_horizontal() {\n                        item.layout.margin.left + item.layout.margin.right\n                    } else {\n                        item.layout.margin.top + item.layout.margin.bottom\n                    };\n                let local_cross = if unit.direction.is_horizontal() {\n                    self.calc_unit_min_height(size_available, mapping, &item.slot)\n                } else {\n                    self.calc_unit_min_width(size_available, mapping, &item.slot)\n                };\n                let local_cross = local_cross\n                    + if unit.direction.is_horizontal() {\n                        item.layout.margin.top + item.layout.margin.bottom\n                    } else {\n                        item.layout.margin.left + item.layout.margin.right\n                    };\n                if !line.is_empty() && main + local_main > main_available {\n                    main += line.len().saturating_sub(1) as Scalar * unit.separation;\n                    lines.push((main, cross, grow, std::mem::take(&mut line)));\n                    main = 0.0;\n                    cross = 0.0;\n                    grow = 0.0;\n                }\n                main += local_main;\n                cross = cross.max(local_cross);\n                grow += item.layout.grow;\n                line.push((item, local_main, local_cross));\n            }\n            main += line.len().saturating_sub(1) as Scalar * unit.separation;\n            lines.push((main, cross, grow, line));\n            (lines, count)\n        };\n        let mut children = Vec::with_capacity(count);\n        let mut main_max: Scalar = 0.0;\n        let mut cross_max = 0.0;\n        for (main, cross_available, grow, items) in lines {\n            let diff = main_available - main;\n            let mut new_main = 0.0;\n            let mut new_cross: Scalar = 0.0;\n            for (item, local_main, local_cross) in items {\n                let child_main = if main < main_available {\n                    local_main\n                        + if grow > 0.0 {\n                            diff * item.layout.grow / grow\n                        } else {\n                            0.0\n                        }\n                } else {\n                    local_main\n                };\n                let child_main = (child_main\n                    - if unit.direction.is_horizontal() {\n                        item.layout.margin.left + item.layout.margin.right\n                    } else {\n                        item.layout.margin.top + item.layout.margin.bottom\n                    })\n                .max(0.0);\n                let child_cross = (local_cross\n                    - if unit.direction.is_horizontal() {\n                        item.layout.margin.top + item.layout.margin.bottom\n                    } else {\n                        item.layout.margin.left + item.layout.margin.right\n                    })\n                .max(0.0);\n                let child_cross = lerp(child_cross, cross_available, item.layout.fill);\n                let rect = if unit.direction.is_horizontal() {\n                    Vec2 {\n                        x: child_main,\n                        y: child_cross,\n                    }\n                } else {\n                    Vec2 {\n                        x: child_cross,\n                        y: child_main,\n                    }\n                };\n                if let Some(mut child) = self.layout_node(rect, mapping, &item.slot) {\n                    if unit.direction.is_horizontal() {\n                        if unit.direction.is_order_ascending() {\n                            child.local_space.left += new_main + item.layout.margin.left;\n                            child.local_space.right += new_main + item.layout.margin.left;\n                        } else {\n                            let left = child.local_space.left;\n                            let right = child.local_space.right;\n                            child.local_space.left =\n                                size_available.x - right - new_main - item.layout.margin.right;\n                            child.local_space.right =\n                                size_available.x - left - new_main - item.layout.margin.right;\n                        }\n                        new_main += rect.x + item.layout.margin.left + item.layout.margin.right;\n                        let diff = lerp(\n                            0.0,\n                            cross_available - child.local_space.height(),\n                            item.layout.align,\n                        );\n                        child.local_space.top += cross_max + item.layout.margin.top + diff;\n                        child.local_space.bottom += cross_max + item.layout.margin.top + diff;\n                        new_cross = new_cross.max(rect.y);\n                    } else {\n                        if unit.direction.is_order_ascending() {\n                            child.local_space.top += new_main + item.layout.margin.top;\n                            child.local_space.bottom += new_main + item.layout.margin.top;\n                        } else {\n                            let top = child.local_space.top;\n                            let bottom = child.local_space.bottom;\n                            child.local_space.top =\n                                size_available.y - bottom - new_main - item.layout.margin.bottom;\n                            child.local_space.bottom =\n                                size_available.y - top - new_main - item.layout.margin.bottom;\n                        }\n                        new_main += rect.y + item.layout.margin.top + item.layout.margin.bottom;\n                        let diff = lerp(\n                            0.0,\n                            cross_available - child.local_space.width(),\n                            item.layout.align,\n                        );\n                        child.local_space.left += cross_max + item.layout.margin.left + diff;\n                        child.local_space.right += cross_max + item.layout.margin.left + diff;\n                        new_cross = new_cross.max(rect.x);\n                    }\n                    new_main += unit.separation;\n                    children.push(child);\n                }\n            }\n            new_main = (new_main - unit.separation).max(0.0);\n            main_max = main_max.max(new_main);\n            cross_max += new_cross + unit.separation;\n        }\n        cross_max = (cross_max - unit.separation).max(0.0);\n        let local_space = if unit.direction.is_horizontal() {\n            Rect {\n                left: 0.0,\n                right: main_max,\n                top: 0.0,\n                bottom: cross_max,\n            }\n        } else {\n            Rect {\n                left: 0.0,\n                right: cross_max,\n                top: 0.0,\n                bottom: main_max,\n            }\n        };\n        LayoutNode {\n            id: unit.id.to_owned(),\n            local_space,\n            children,\n        }\n    }\n\n    pub fn layout_flex_box_no_wrap(\n        &self,\n        size_available: Vec2,\n        mapping: &CoordsMapping,\n        unit: &FlexBox,\n    ) -> LayoutNode {\n        let (main_available, cross_available) = if unit.direction.is_horizontal() {\n            (size_available.x, size_available.y)\n        } else {\n            (size_available.y, size_available.x)\n        };\n        let mut main = 0.0;\n        let mut cross: Scalar = 0.0;\n        let mut grow = 0.0;\n        let mut shrink = 0.0;\n        let items = unit\n            .items\n            .iter()\n            .filter(|item| item.slot.is_some() && item.slot.as_data().unwrap().id().is_valid())\n            .collect::<Vec<_>>();\n        let mut axis_sizes = Vec::with_capacity(items.len());\n        for item in &items {\n            let local_main = item.layout.basis.unwrap_or_else(|| {\n                if unit.direction.is_horizontal() {\n                    self.calc_unit_min_width(size_available, mapping, &item.slot)\n                } else {\n                    self.calc_unit_min_height(size_available, mapping, &item.slot)\n                }\n            });\n            let local_main = local_main\n                + if unit.direction.is_horizontal() {\n                    item.layout.margin.left + item.layout.margin.right\n                } else {\n                    item.layout.margin.top + item.layout.margin.bottom\n                };\n            let local_cross = if unit.direction.is_horizontal() {\n                self.calc_unit_min_height(size_available, mapping, &item.slot)\n            } else {\n                self.calc_unit_min_width(size_available, mapping, &item.slot)\n            };\n            let local_cross = local_cross\n                + if unit.direction.is_horizontal() {\n                    item.layout.margin.top + item.layout.margin.bottom\n                } else {\n                    item.layout.margin.left + item.layout.margin.right\n                };\n            let local_cross = lerp(local_cross, cross_available, item.layout.fill);\n            main += local_main;\n            cross = cross.max(local_cross);\n            grow += item.layout.grow;\n            shrink += item.layout.shrink;\n            axis_sizes.push((local_main, local_cross));\n        }\n        main += items.len().saturating_sub(1) as Scalar * unit.separation;\n        let diff = main_available - main;\n        let mut new_main = 0.0;\n        let mut new_cross: Scalar = 0.0;\n        let children = items\n            .into_iter()\n            .zip(axis_sizes)\n            .filter_map(|(item, axis_size)| {\n                let child_main = if main < main_available {\n                    axis_size.0\n                        + if grow > 0.0 {\n                            diff * item.layout.grow / grow\n                        } else {\n                            0.0\n                        }\n                } else if main > main_available {\n                    axis_size.0\n                        + if shrink > 0.0 {\n                            diff * item.layout.shrink / shrink\n                        } else {\n                            0.0\n                        }\n                } else {\n                    axis_size.0\n                };\n                let child_main = (child_main\n                    - if unit.direction.is_horizontal() {\n                        item.layout.margin.left + item.layout.margin.right\n                    } else {\n                        item.layout.margin.top + item.layout.margin.bottom\n                    })\n                .max(0.0);\n                let child_cross = (axis_size.1\n                    - if unit.direction.is_horizontal() {\n                        item.layout.margin.top + item.layout.margin.bottom\n                    } else {\n                        item.layout.margin.left + item.layout.margin.right\n                    })\n                .max(0.0);\n                let rect = if unit.direction.is_horizontal() {\n                    Vec2 {\n                        x: child_main,\n                        y: child_cross,\n                    }\n                } else {\n                    Vec2 {\n                        x: child_cross,\n                        y: child_main,\n                    }\n                };\n                if let Some(mut child) = self.layout_node(rect, mapping, &item.slot) {\n                    if unit.direction.is_horizontal() {\n                        if unit.direction.is_order_ascending() {\n                            child.local_space.left += new_main + item.layout.margin.left;\n                            child.local_space.right += new_main + item.layout.margin.left;\n                        } else {\n                            let left = child.local_space.left;\n                            let right = child.local_space.right;\n                            child.local_space.left =\n                                size_available.x - right - new_main - item.layout.margin.right;\n                            child.local_space.right =\n                                size_available.x - left - new_main - item.layout.margin.right;\n                        }\n                        new_main += rect.x + item.layout.margin.left + item.layout.margin.right;\n                        let diff = lerp(\n                            0.0,\n                            cross_available - child.local_space.height(),\n                            item.layout.align,\n                        );\n                        child.local_space.top += item.layout.margin.top + diff;\n                        child.local_space.bottom += item.layout.margin.top + diff;\n                        new_cross = new_cross.max(rect.y);\n                    } else {\n                        if unit.direction.is_order_ascending() {\n                            child.local_space.top += new_main + item.layout.margin.top;\n                            child.local_space.bottom += new_main + item.layout.margin.top;\n                        } else {\n                            let top = child.local_space.top;\n                            let bottom = child.local_space.bottom;\n                            child.local_space.top =\n                                size_available.y - bottom - new_main - item.layout.margin.bottom;\n                            child.local_space.bottom =\n                                size_available.y - top - new_main - item.layout.margin.bottom;\n                        }\n                        new_main += rect.y + item.layout.margin.top + item.layout.margin.bottom;\n                        let diff = lerp(\n                            0.0,\n                            cross_available - child.local_space.width(),\n                            item.layout.align,\n                        );\n                        child.local_space.left += item.layout.margin.left + diff;\n                        child.local_space.right += item.layout.margin.left + diff;\n                        new_cross = new_cross.max(rect.x);\n                    }\n                    new_main += unit.separation;\n                    Some(child)\n                } else {\n                    None\n                }\n            })\n            .collect::<Vec<_>>();\n        new_main = (new_main - unit.separation).max(0.0);\n        let local_space = if unit.direction.is_horizontal() {\n            Rect {\n                left: 0.0,\n                right: new_main,\n                top: 0.0,\n                bottom: new_cross,\n            }\n        } else {\n            Rect {\n                left: 0.0,\n                right: new_cross,\n                top: 0.0,\n                bottom: new_main,\n            }\n        };\n        LayoutNode {\n            id: unit.id.to_owned(),\n            local_space,\n            children,\n        }\n    }\n\n    pub fn layout_grid_box(\n        &self,\n        size_available: Vec2,\n        mapping: &CoordsMapping,\n        unit: &GridBox,\n    ) -> Option<LayoutNode> {\n        if !unit.id.is_valid() {\n            return None;\n        }\n        let cell_width = if unit.cols > 0 {\n            size_available.x / unit.cols as Scalar\n        } else {\n            0.0\n        };\n        let cell_height = if unit.rows > 0 {\n            size_available.y / unit.rows as Scalar\n        } else {\n            0.0\n        };\n        let children = unit\n            .items\n            .iter()\n            .filter_map(|item| {\n                let left = item.layout.space_occupancy.left as Scalar * cell_width;\n                let right = item.layout.space_occupancy.right as Scalar * cell_width;\n                let top = item.layout.space_occupancy.top as Scalar * cell_height;\n                let bottom = item.layout.space_occupancy.bottom as Scalar * cell_height;\n                let width =\n                    (right - left - item.layout.margin.left - item.layout.margin.right).max(0.0);\n                let height =\n                    (bottom - top - item.layout.margin.top - item.layout.margin.bottom).max(0.0);\n                let size = Vec2 {\n                    x: width,\n                    y: height,\n                };\n                if let Some(mut child) = self.layout_node(size, mapping, &item.slot) {\n                    let diff = size.x - child.local_space.width();\n                    let ox = lerp(0.0, diff, item.layout.horizontal_align);\n                    let diff = size.y - child.local_space.height();\n                    let oy = lerp(0.0, diff, item.layout.vertical_align);\n                    child.local_space.left += left + item.layout.margin.left - ox;\n                    child.local_space.right += left + item.layout.margin.left - ox;\n                    child.local_space.top += top + item.layout.margin.top - oy;\n                    child.local_space.bottom += top + item.layout.margin.top - oy;\n                    Some(child)\n                } else {\n                    None\n                }\n            })\n            .collect::<Vec<_>>();\n        Some(LayoutNode {\n            id: unit.id.to_owned(),\n            local_space: Rect {\n                left: 0.0,\n                right: size_available.x,\n                top: 0.0,\n                bottom: size_available.y,\n            },\n            children,\n        })\n    }\n\n    pub fn layout_size_box(\n        &self,\n        size_available: Vec2,\n        mapping: &CoordsMapping,\n        unit: &SizeBox,\n    ) -> Option<LayoutNode> {\n        if !unit.id.is_valid() {\n            return None;\n        }\n        let mut size = Vec2 {\n            x: match unit.width {\n                SizeBoxSizeValue::Content => {\n                    self.calc_unit_min_width(size_available, mapping, &unit.slot)\n                }\n                SizeBoxSizeValue::Fill => size_available.x - unit.margin.left - unit.margin.right,\n                SizeBoxSizeValue::Exact(v) => v,\n            },\n            y: match unit.height {\n                SizeBoxSizeValue::Content => {\n                    self.calc_unit_min_height(size_available, mapping, &unit.slot)\n                }\n                SizeBoxSizeValue::Fill => size_available.y - unit.margin.top - unit.margin.bottom,\n                SizeBoxSizeValue::Exact(v) => v,\n            },\n        };\n        match unit.keep_aspect_ratio {\n            SizeBoxAspectRatio::None => {}\n            SizeBoxAspectRatio::WidthOfHeight(factor) => {\n                size.x = (size.y * factor).max(0.0);\n            }\n            SizeBoxAspectRatio::HeightOfWidth(factor) => {\n                size.y = (size.x * factor).max(0.0);\n            }\n        }\n        let children = if let Some(mut child) = self.layout_node(size, mapping, &unit.slot) {\n            child.local_space.left += unit.margin.left;\n            child.local_space.right += unit.margin.left;\n            child.local_space.top += unit.margin.top;\n            child.local_space.bottom += unit.margin.top;\n            vec![child]\n        } else {\n            vec![]\n        };\n        let local_space = Rect {\n            left: 0.0,\n            right: size.x,\n            top: 0.0,\n            bottom: size.y,\n        };\n        Some(LayoutNode {\n            id: unit.id.to_owned(),\n            local_space,\n            children,\n        })\n    }\n\n    pub fn layout_image_box(&self, size_available: Vec2, unit: &ImageBox) -> Option<LayoutNode> {\n        if !unit.id.is_valid() {\n            return None;\n        }\n        let local_space = Rect {\n            left: 0.0,\n            right: match unit.width {\n                ImageBoxSizeValue::Fill => size_available.x,\n                ImageBoxSizeValue::Exact(v) => v,\n            },\n            top: 0.0,\n            bottom: match unit.height {\n                ImageBoxSizeValue::Fill => size_available.y,\n                ImageBoxSizeValue::Exact(v) => v,\n            },\n        };\n        Some(LayoutNode {\n            id: unit.id.to_owned(),\n            local_space,\n            children: vec![],\n        })\n    }\n\n    pub fn layout_text_box(\n        &self,\n        size_available: Vec2,\n        mapping: &CoordsMapping,\n        unit: &TextBox,\n    ) -> Option<LayoutNode> {\n        if !unit.id.is_valid() {\n            return None;\n        }\n        let aabb = self\n            .text_measurement_engine\n            .measure_text(size_available, mapping, unit)\n            .unwrap_or_default();\n        let local_space = Rect {\n            left: 0.0,\n            right: match unit.width {\n                TextBoxSizeValue::Content => aabb.width(),\n                TextBoxSizeValue::Fill => size_available.x,\n                TextBoxSizeValue::Exact(v) => v,\n            },\n            top: 0.0,\n            bottom: match unit.height {\n                TextBoxSizeValue::Content => aabb.height(),\n                TextBoxSizeValue::Fill => size_available.y,\n                TextBoxSizeValue::Exact(v) => v,\n            },\n        };\n        Some(LayoutNode {\n            id: unit.id.to_owned(),\n            local_space,\n            children: vec![],\n        })\n    }\n\n    fn calc_unit_min_width(\n        &self,\n        size_available: Vec2,\n        mapping: &CoordsMapping,\n        unit: &WidgetUnit,\n    ) -> Scalar {\n        match unit {\n            WidgetUnit::None | WidgetUnit::PortalBox(_) => 0.0,\n            WidgetUnit::AreaBox(b) => self.calc_unit_min_width(size_available, mapping, &b.slot),\n            WidgetUnit::ContentBox(b) => {\n                self.calc_content_box_min_width(size_available, mapping, b)\n            }\n            WidgetUnit::FlexBox(b) => self.calc_flex_box_min_width(size_available, mapping, b),\n            WidgetUnit::GridBox(b) => self.calc_grid_box_min_width(size_available, mapping, b),\n            WidgetUnit::SizeBox(b) => {\n                (match b.width {\n                    SizeBoxSizeValue::Content => {\n                        self.calc_unit_min_width(size_available, mapping, &b.slot)\n                    }\n                    SizeBoxSizeValue::Fill => 0.0,\n                    SizeBoxSizeValue::Exact(v) => v,\n                }) + b.margin.left\n                    + b.margin.right\n            }\n            WidgetUnit::ImageBox(b) => match b.width {\n                ImageBoxSizeValue::Fill => 0.0,\n                ImageBoxSizeValue::Exact(v) => v,\n            },\n            WidgetUnit::TextBox(b) => {\n                let aabb = self\n                    .text_measurement_engine\n                    .measure_text(size_available, mapping, b)\n                    .unwrap_or_default();\n                match b.width {\n                    TextBoxSizeValue::Content => aabb.width(),\n                    TextBoxSizeValue::Fill => 0.0,\n                    TextBoxSizeValue::Exact(v) => v,\n                }\n            }\n        }\n    }\n\n    fn calc_content_box_min_width(\n        &self,\n        size_available: Vec2,\n        mapping: &CoordsMapping,\n        unit: &ContentBox,\n    ) -> Scalar {\n        let mut result: Scalar = 0.0;\n        for item in &unit.items {\n            let size = self.calc_unit_min_width(size_available, mapping, &item.slot)\n                + item.layout.margin.left\n                + item.layout.margin.right;\n            let width = item.layout.anchors.right - item.layout.anchors.left;\n            let size = if width > 0.0 { size / width } else { 0.0 };\n            result = result.max(size);\n        }\n        result\n    }\n\n    fn calc_flex_box_min_width(\n        &self,\n        size_available: Vec2,\n        mapping: &CoordsMapping,\n        unit: &FlexBox,\n    ) -> Scalar {\n        if unit.direction.is_horizontal() {\n            self.calc_horizontal_flex_box_min_width(size_available, mapping, unit)\n        } else {\n            self.calc_vertical_flex_box_min_width(size_available, mapping, unit)\n        }\n    }\n\n    fn calc_horizontal_flex_box_min_width(\n        &self,\n        size_available: Vec2,\n        mapping: &CoordsMapping,\n        unit: &FlexBox,\n    ) -> Scalar {\n        if unit.wrap {\n            let mut result: Scalar = 0.0;\n            let mut line = 0.0;\n            let mut first = true;\n            for item in &unit.items {\n                let size = self.calc_unit_min_width(size_available, mapping, &item.slot)\n                    + item.layout.margin.left\n                    + item.layout.margin.right;\n                if first || line + size <= size_available.x {\n                    line += size;\n                    if !first {\n                        line += unit.separation;\n                    }\n                    first = false;\n                } else {\n                    result = result.max(line);\n                    line = 0.0;\n                    first = true;\n                }\n            }\n            result.max(line)\n        } else {\n            let mut result = 0.0;\n            for item in &unit.items {\n                result += self.calc_unit_min_width(size_available, mapping, &item.slot)\n                    + item.layout.margin.left\n                    + item.layout.margin.right;\n            }\n            result + (unit.items.len().saturating_sub(1) as Scalar) * unit.separation\n        }\n    }\n\n    fn calc_vertical_flex_box_min_width(\n        &self,\n        size_available: Vec2,\n        mapping: &CoordsMapping,\n        unit: &FlexBox,\n    ) -> Scalar {\n        if unit.wrap {\n            let mut result = 0.0;\n            let mut line_length = 0.0;\n            let mut line: Scalar = 0.0;\n            let mut lines: usize = 0;\n            let mut first = true;\n            for item in &unit.items {\n                let width = self.calc_unit_min_width(size_available, mapping, &item.slot)\n                    + item.layout.margin.left\n                    + item.layout.margin.right;\n                let height = self.calc_unit_min_height(size_available, mapping, &item.slot)\n                    + item.layout.margin.top\n                    + item.layout.margin.bottom;\n                if first || line_length + height <= size_available.y {\n                    line_length += height;\n                    if !first {\n                        line_length += unit.separation;\n                    }\n                    line = line.max(width);\n                    first = false;\n                } else {\n                    result += line;\n                    line_length = 0.0;\n                    line = 0.0;\n                    lines += 1;\n                    first = true;\n                }\n            }\n            result += line;\n            lines += 1;\n            result + (lines.saturating_sub(1) as Scalar) * unit.separation\n        } else {\n            unit.items.iter().fold(0.0, |a, item| {\n                (self.calc_unit_min_width(size_available, mapping, &item.slot)\n                    + item.layout.margin.left\n                    + item.layout.margin.right)\n                    .max(a)\n            })\n        }\n    }\n\n    fn calc_grid_box_min_width(\n        &self,\n        size_available: Vec2,\n        mapping: &CoordsMapping,\n        unit: &GridBox,\n    ) -> Scalar {\n        let mut result: Scalar = 0.0;\n        for item in &unit.items {\n            let size = self.calc_unit_min_width(size_available, mapping, &item.slot)\n                + item.layout.margin.left\n                + item.layout.margin.right;\n            let size = if size > 0.0 {\n                (item.layout.space_occupancy.width() as Scalar * size) / unit.cols as Scalar\n            } else {\n                0.0\n            };\n            result = result.max(size);\n        }\n        result\n    }\n\n    fn calc_unit_min_height(\n        &self,\n        size_available: Vec2,\n        mapping: &CoordsMapping,\n        unit: &WidgetUnit,\n    ) -> Scalar {\n        match unit {\n            WidgetUnit::None | WidgetUnit::PortalBox(_) => 0.0,\n            WidgetUnit::AreaBox(b) => self.calc_unit_min_height(size_available, mapping, &b.slot),\n            WidgetUnit::ContentBox(b) => {\n                self.calc_content_box_min_height(size_available, mapping, b)\n            }\n            WidgetUnit::FlexBox(b) => self.calc_flex_box_min_height(size_available, mapping, b),\n            WidgetUnit::GridBox(b) => self.calc_grid_box_min_height(size_available, mapping, b),\n            WidgetUnit::SizeBox(b) => {\n                (match b.height {\n                    SizeBoxSizeValue::Content => {\n                        self.calc_unit_min_height(size_available, mapping, &b.slot)\n                    }\n                    SizeBoxSizeValue::Fill => 0.0,\n                    SizeBoxSizeValue::Exact(v) => v,\n                }) + b.margin.top\n                    + b.margin.bottom\n            }\n            WidgetUnit::ImageBox(b) => match b.height {\n                ImageBoxSizeValue::Fill => 0.0,\n                ImageBoxSizeValue::Exact(v) => v,\n            },\n            WidgetUnit::TextBox(b) => {\n                let aabb = self\n                    .text_measurement_engine\n                    .measure_text(size_available, mapping, b)\n                    .unwrap_or_default();\n                match b.height {\n                    TextBoxSizeValue::Content => aabb.height(),\n                    TextBoxSizeValue::Fill => 0.0,\n                    TextBoxSizeValue::Exact(v) => v,\n                }\n            }\n        }\n    }\n\n    fn calc_content_box_min_height(\n        &self,\n        size_available: Vec2,\n        mapping: &CoordsMapping,\n        unit: &ContentBox,\n    ) -> Scalar {\n        let mut result: Scalar = 0.0;\n        for item in &unit.items {\n            let size = self.calc_unit_min_height(size_available, mapping, &item.slot)\n                + item.layout.margin.top\n                + item.layout.margin.bottom;\n            let height = item.layout.anchors.bottom - item.layout.anchors.top;\n            let size = if height > 0.0 { size / height } else { 0.0 };\n            result = result.max(size);\n        }\n        result\n    }\n\n    fn calc_flex_box_min_height(\n        &self,\n        size_available: Vec2,\n        mapping: &CoordsMapping,\n        unit: &FlexBox,\n    ) -> Scalar {\n        if unit.direction.is_horizontal() {\n            self.calc_horizontal_flex_box_min_height(size_available, mapping, unit)\n        } else {\n            self.calc_vertical_flex_box_min_height(size_available, mapping, unit)\n        }\n    }\n\n    fn calc_horizontal_flex_box_min_height(\n        &self,\n        size_available: Vec2,\n        mapping: &CoordsMapping,\n        unit: &FlexBox,\n    ) -> Scalar {\n        if unit.wrap {\n            let mut result = 0.0;\n            let mut line_length = 0.0;\n            let mut line: Scalar = 0.0;\n            let mut lines: usize = 0;\n            let mut first = true;\n            for item in &unit.items {\n                let width = self.calc_unit_min_width(size_available, mapping, &item.slot)\n                    + item.layout.margin.left\n                    + item.layout.margin.right;\n                let height = self.calc_unit_min_height(size_available, mapping, &item.slot)\n                    + item.layout.margin.top\n                    + item.layout.margin.bottom;\n                if first || line_length + width <= size_available.x {\n                    line_length += width;\n                    if !first {\n                        line_length += unit.separation;\n                    }\n                    line = line.max(height);\n                    first = false;\n                } else {\n                    result += line;\n                    line_length = 0.0;\n                    line = 0.0;\n                    lines += 1;\n                    first = true;\n                }\n            }\n            result += line;\n            lines += 1;\n            result + (lines.saturating_sub(1) as Scalar) * unit.separation\n        } else {\n            unit.items.iter().fold(0.0, |a, item| {\n                (self.calc_unit_min_height(size_available, mapping, &item.slot)\n                    + item.layout.margin.top\n                    + item.layout.margin.bottom)\n                    .max(a)\n            })\n        }\n    }\n\n    fn calc_vertical_flex_box_min_height(\n        &self,\n        size_available: Vec2,\n        mapping: &CoordsMapping,\n        unit: &FlexBox,\n    ) -> Scalar {\n        if unit.wrap {\n            let mut result: Scalar = 0.0;\n            let mut line = 0.0;\n            let mut first = true;\n            for item in &unit.items {\n                let size = self.calc_unit_min_height(size_available, mapping, &item.slot)\n                    + item.layout.margin.top\n                    + item.layout.margin.bottom;\n                if first || line + size <= size_available.y {\n                    line += size;\n                    if !first {\n                        line += unit.separation;\n                    }\n                    first = false;\n                } else {\n                    result = result.max(line);\n                    line = 0.0;\n                    first = true;\n                }\n            }\n            result.max(line)\n        } else {\n            let mut result = 0.0;\n            for item in &unit.items {\n                result += self.calc_unit_min_height(size_available, mapping, &item.slot)\n                    + item.layout.margin.top\n                    + item.layout.margin.bottom;\n            }\n            result + (unit.items.len().saturating_sub(1) as Scalar) * unit.separation\n        }\n    }\n\n    fn calc_grid_box_min_height(\n        &self,\n        size_available: Vec2,\n        mapping: &CoordsMapping,\n        unit: &GridBox,\n    ) -> Scalar {\n        let mut result: Scalar = 0.0;\n        for item in &unit.items {\n            let size = self.calc_unit_min_height(size_available, mapping, &item.slot)\n                + item.layout.margin.top\n                + item.layout.margin.bottom;\n            let size = if size > 0.0 {\n                (item.layout.space_occupancy.height() as Scalar * size) / unit.cols as Scalar\n            } else {\n                0.0\n            };\n            result = result.max(size);\n        }\n        result\n    }\n\n    fn unpack_node(\n        parent: Option<&WidgetId>,\n        ui_space: Rect,\n        node: LayoutNode,\n        items: &mut HashMap<WidgetId, LayoutItem>,\n    ) {\n        let LayoutNode {\n            id,\n            local_space,\n            children,\n        } = node;\n        let ui_space = Rect {\n            left: local_space.left + ui_space.left,\n            right: local_space.right + ui_space.left,\n            top: local_space.top + ui_space.top,\n            bottom: local_space.bottom + ui_space.top,\n        };\n        for node in children {\n            Self::unpack_node(Some(&id), ui_space, node, items);\n        }\n        items.insert(\n            id,\n            LayoutItem {\n                local_space,\n                ui_space,\n                parent: parent.cloned(),\n            },\n        );\n    }\n}\n\nimpl<TME: TextMeasurementEngine> LayoutEngine<()> for DefaultLayoutEngine<TME> {\n    fn layout(&mut self, mapping: &CoordsMapping, tree: &WidgetUnit) -> Result<Layout, ()> {\n        let ui_space = mapping.virtual_area();\n        if let Some(root) = self.layout_node(ui_space.size(), mapping, tree) {\n            let mut items = HashMap::with_capacity(root.count());\n            Self::unpack_node(None, ui_space, root, &mut items);\n            Ok(Layout { ui_space, items })\n        } else {\n            Ok(Layout {\n                ui_space,\n                items: Default::default(),\n            })\n        }\n    }\n}\n"
  },
  {
    "path": "crates/core/src/layout/mod.rs",
    "content": "//! Layout engine\n\npub mod default_layout_engine;\n\nuse crate::{\n    Scalar,\n    widget::{\n        WidgetId,\n        unit::WidgetUnit,\n        utils::{Rect, Vec2},\n    },\n};\nuse serde::{Deserialize, Serialize};\nuse std::collections::HashMap;\n\npub trait LayoutEngine<E> {\n    fn layout(&mut self, mapping: &CoordsMapping, tree: &WidgetUnit) -> Result<Layout, E>;\n}\n\nstruct LayoutSortedItems<'a>(Vec<(&'a WidgetId, &'a LayoutItem)>);\n\nimpl<'a> LayoutSortedItems<'a> {\n    fn new(items: &'a HashMap<WidgetId, LayoutItem>) -> Self {\n        let mut items = items.iter().collect::<Vec<_>>();\n        items.sort_by(|a, b| a.0.path().cmp(b.0.path()));\n        Self(items)\n    }\n}\n\nimpl std::fmt::Debug for LayoutSortedItems<'_> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_map()\n            .entries(self.0.iter().map(|&(k, v)| (k, v)))\n            .finish()\n    }\n}\n\n#[derive(Default, Clone, Serialize, Deserialize)]\npub struct Layout {\n    pub ui_space: Rect,\n    pub items: HashMap<WidgetId, LayoutItem>,\n}\n\nimpl std::fmt::Debug for Layout {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"Layout\")\n            .field(\"ui_space\", &self.ui_space)\n            .field(\"items\", &LayoutSortedItems::new(&self.items))\n            .finish()\n    }\n}\n\nimpl Layout {\n    pub fn find(&self, mut path: &str) -> Option<&LayoutItem> {\n        loop {\n            if let Some(item) = self\n                .items\n                .iter()\n                .find_map(|(k, v)| if k.path() == path { Some(v) } else { None })\n            {\n                return Some(item);\n            } else if let Some(index) = path.rfind('/') {\n                path = &path[0..index];\n            } else {\n                break;\n            }\n        }\n        None\n    }\n\n    pub fn find_or_ui_space(&self, path: &str) -> LayoutItem {\n        match self.find(path) {\n            Some(item) => item.to_owned(),\n            None => LayoutItem {\n                local_space: self.ui_space,\n                ui_space: self.ui_space,\n                parent: None,\n            },\n        }\n    }\n\n    pub fn virtual_to_real(&self, mapping: &CoordsMapping) -> Self {\n        Self {\n            ui_space: mapping.virtual_to_real_rect(self.ui_space, false),\n            items: self\n                .items\n                .iter()\n                .map(|(k, v)| (k.to_owned(), v.virtual_to_real(mapping)))\n                .collect::<HashMap<_, _>>(),\n        }\n    }\n\n    pub fn real_to_virtual(&self, mapping: &CoordsMapping) -> Self {\n        Self {\n            ui_space: mapping.real_to_virtual_rect(self.ui_space, false),\n            items: self\n                .items\n                .iter()\n                .map(|(k, v)| (k.to_owned(), v.real_to_virtual(mapping)))\n                .collect::<HashMap<_, _>>(),\n        }\n    }\n\n    pub fn rect_relative_to(&self, id: &WidgetId, to: &WidgetId) -> Option<Rect> {\n        let a = self.items.get(id)?;\n        let b = self.items.get(to)?;\n        let x = a.ui_space.left - b.ui_space.left;\n        let y = a.ui_space.top - b.ui_space.top;\n        Some(Rect {\n            left: x,\n            right: x + a.ui_space.width(),\n            top: y,\n            bottom: y + a.ui_space.height(),\n        })\n    }\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct LayoutNode {\n    pub id: WidgetId,\n    pub local_space: Rect,\n    pub children: Vec<LayoutNode>,\n}\n\nimpl LayoutNode {\n    pub fn count(&self) -> usize {\n        1 + self.children.iter().map(Self::count).sum::<usize>()\n    }\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct LayoutItem {\n    pub local_space: Rect,\n    pub ui_space: Rect,\n    pub parent: Option<WidgetId>,\n}\n\nimpl LayoutItem {\n    pub fn virtual_to_real(&self, mapping: &CoordsMapping) -> Self {\n        Self {\n            local_space: mapping.virtual_to_real_rect(self.local_space, true),\n            ui_space: mapping.virtual_to_real_rect(self.ui_space, false),\n            parent: self.parent.to_owned(),\n        }\n    }\n\n    pub fn real_to_virtual(&self, mapping: &CoordsMapping) -> Self {\n        Self {\n            local_space: mapping.real_to_virtual_rect(self.local_space, true),\n            ui_space: mapping.real_to_virtual_rect(self.ui_space, false),\n            parent: self.parent.to_owned(),\n        }\n    }\n}\n\nimpl LayoutEngine<()> for () {\n    fn layout(&mut self, mapping: &CoordsMapping, _: &WidgetUnit) -> Result<Layout, ()> {\n        Ok(Layout {\n            ui_space: mapping.virtual_area(),\n            items: Default::default(),\n        })\n    }\n}\n\n#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize)]\npub enum CoordsMappingScaling {\n    #[default]\n    None,\n    Constant(Scalar),\n    Stretch(Vec2),\n    FitHorizontal(Scalar),\n    FitVertical(Scalar),\n    FitMinimum(Vec2),\n    FitMaximum(Vec2),\n    FitToView(Vec2, bool),\n}\n\n#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]\npub struct CoordsMapping {\n    #[serde(default)]\n    scale: Vec2,\n    #[serde(default)]\n    offset: Vec2,\n    #[serde(default)]\n    real_area: Rect,\n    #[serde(default)]\n    virtual_area: Rect,\n}\n\nimpl Default for CoordsMapping {\n    fn default() -> Self {\n        Self::new(Default::default())\n    }\n}\n\nimpl CoordsMapping {\n    pub fn new(real_area: Rect) -> Self {\n        Self::new_scaling(real_area, CoordsMappingScaling::None)\n    }\n\n    pub fn new_scaling(real_area: Rect, scaling: CoordsMappingScaling) -> Self {\n        match scaling {\n            CoordsMappingScaling::None => Self {\n                scale: 1.0.into(),\n                offset: Vec2::default(),\n                real_area,\n                virtual_area: Rect {\n                    left: 0.0,\n                    right: real_area.width(),\n                    top: 0.0,\n                    bottom: real_area.height(),\n                },\n            },\n            CoordsMappingScaling::Constant(value) => {\n                let value = if value > 0.0 { value } else { 1.0 };\n                Self {\n                    scale: value.into(),\n                    offset: Vec2::default(),\n                    real_area,\n                    virtual_area: Rect {\n                        left: 0.0,\n                        right: real_area.width() / value,\n                        top: 0.0,\n                        bottom: real_area.height() / value,\n                    },\n                }\n            }\n            CoordsMappingScaling::Stretch(size) => {\n                let vw = size.x;\n                let vh = size.y;\n                let rw = real_area.width();\n                let rh = real_area.height();\n                let scale_x = rw / vw;\n                let scale_y = rh / vh;\n                let w = vw * scale_x;\n                let h = vh * scale_y;\n                Self {\n                    scale: Vec2 {\n                        x: scale_x,\n                        y: scale_y,\n                    },\n                    offset: Vec2 {\n                        x: (rw - w) * 0.5,\n                        y: (rh - h) * 0.5,\n                    },\n                    real_area,\n                    virtual_area: Rect {\n                        left: 0.0,\n                        right: vw,\n                        top: 0.0,\n                        bottom: vh,\n                    },\n                }\n            }\n            CoordsMappingScaling::FitHorizontal(vw) => {\n                let rw = real_area.width();\n                let rh = real_area.height();\n                let scale = rw / vw;\n                let vh = rh / scale;\n                Self {\n                    scale: scale.into(),\n                    offset: Vec2::default(),\n                    real_area,\n                    virtual_area: Rect {\n                        left: 0.0,\n                        right: vw,\n                        top: 0.0,\n                        bottom: vh,\n                    },\n                }\n            }\n            CoordsMappingScaling::FitVertical(vh) => {\n                let rw = real_area.width();\n                let rh = real_area.height();\n                let scale = rh / vh;\n                let vw = rw / scale;\n                Self {\n                    scale: scale.into(),\n                    offset: Vec2::default(),\n                    real_area,\n                    virtual_area: Rect {\n                        left: 0.0,\n                        right: vw,\n                        top: 0.0,\n                        bottom: vh,\n                    },\n                }\n            }\n            CoordsMappingScaling::FitMinimum(size) => {\n                if size.x < size.y {\n                    Self::new_scaling(real_area, CoordsMappingScaling::FitHorizontal(size.x))\n                } else {\n                    Self::new_scaling(real_area, CoordsMappingScaling::FitVertical(size.y))\n                }\n            }\n            CoordsMappingScaling::FitMaximum(size) => {\n                if size.x > size.y {\n                    Self::new_scaling(real_area, CoordsMappingScaling::FitHorizontal(size.x))\n                } else {\n                    Self::new_scaling(real_area, CoordsMappingScaling::FitVertical(size.y))\n                }\n            }\n            CoordsMappingScaling::FitToView(size, keep_aspect_ratio) => {\n                let rw = real_area.width();\n                let rh = real_area.height();\n                let av = size.x / size.y;\n                let ar = rw / rh;\n                let (scale, vw, vh) = if keep_aspect_ratio {\n                    let vw = size.x;\n                    let vh = size.y;\n                    let scale = if ar >= av { rh / vh } else { rw / vw };\n                    (scale, vw, vh)\n                } else if ar >= av {\n                    (rh / size.y, size.x * rw / rh, size.y)\n                } else {\n                    (rw / size.x, size.x, size.y * rh / rw)\n                };\n                let w = vw * scale;\n                let h = vh * scale;\n                Self {\n                    scale: scale.into(),\n                    offset: Vec2 {\n                        x: (rw - w) * 0.5,\n                        y: (rh - h) * 0.5,\n                    },\n                    real_area,\n                    virtual_area: Rect {\n                        left: 0.0,\n                        right: vw,\n                        top: 0.0,\n                        bottom: vh,\n                    },\n                }\n            }\n        }\n    }\n\n    #[inline]\n    pub fn scale(&self) -> Vec2 {\n        self.scale\n    }\n\n    #[inline]\n    pub fn scalar_scale(&self, max: bool) -> Scalar {\n        if max {\n            self.scale.x.max(self.scale.y)\n        } else {\n            self.scale.x.min(self.scale.y)\n        }\n    }\n\n    #[inline]\n    pub fn offset(&self) -> Vec2 {\n        self.offset\n    }\n\n    #[inline]\n    pub fn virtual_area(&self) -> Rect {\n        self.virtual_area\n    }\n\n    #[inline]\n    pub fn virtual_to_real_vec2(&self, coord: Vec2, local_space: bool) -> Vec2 {\n        if local_space {\n            Vec2 {\n                x: coord.x * self.scale.x,\n                y: coord.y * self.scale.y,\n            }\n        } else {\n            Vec2 {\n                x: self.offset.x + (coord.x * self.scale.x),\n                y: self.offset.y + (coord.y * self.scale.y),\n            }\n        }\n    }\n\n    #[inline]\n    pub fn real_to_virtual_vec2(&self, coord: Vec2, local_space: bool) -> Vec2 {\n        if local_space {\n            Vec2 {\n                x: coord.x / self.scale.x,\n                y: coord.y / self.scale.y,\n            }\n        } else {\n            Vec2 {\n                x: (coord.x - self.offset.x) / self.scale.x,\n                y: (coord.y - self.offset.y) / self.scale.y,\n            }\n        }\n    }\n\n    #[inline]\n    pub fn virtual_to_real_rect(&self, area: Rect, local_space: bool) -> Rect {\n        if local_space {\n            Rect {\n                left: area.left * self.scale.x,\n                right: area.right * self.scale.x,\n                top: area.top * self.scale.y,\n                bottom: area.bottom * self.scale.y,\n            }\n        } else {\n            Rect {\n                left: self.offset.x + (area.left * self.scale.x),\n                right: self.offset.x + (area.right * self.scale.x),\n                top: self.offset.y + (area.top * self.scale.y),\n                bottom: self.offset.y + (area.bottom * self.scale.y),\n            }\n        }\n    }\n\n    #[inline]\n    pub fn real_to_virtual_rect(&self, area: Rect, local_space: bool) -> Rect {\n        if local_space {\n            Rect {\n                left: area.left / self.scale.x,\n                right: area.right / self.scale.x,\n                top: area.top / self.scale.y,\n                bottom: area.bottom / self.scale.y,\n            }\n        } else {\n            Rect {\n                left: (area.left - self.offset.x) / self.scale.x,\n                right: (area.right - self.offset.x) / self.scale.x,\n                top: (area.top - self.offset.y) / self.scale.y,\n                bottom: (area.bottom - self.offset.y) / self.scale.y,\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "crates/core/src/lib.rs",
    "content": "//! RAUI core types and components\n//!\n//! The things that most users will be interested in here are the [components][widget::component].\n//! Those have more documentation on how to use widgets, components, etc. in your app.\n\npub mod application;\n#[macro_use]\npub mod messenger;\n#[macro_use]\npub mod props;\npub mod renderer;\npub mod state;\n#[macro_use]\npub mod widget;\npub mod animator;\npub mod interactive;\npub mod layout;\npub mod signals;\npub mod tester;\npub mod view_model;\n\npub type Scalar = f32;\npub type Integer = i32;\npub type UnsignedInteger = u32;\n\npub use intuicio_data::{\n    lifetime::*,\n    managed::{gc::*, value::*, *},\n    type_hash::*,\n};\npub use raui_derive::*;\nuse serde::{Serialize, de::DeserializeOwned};\n#[doc(inline)]\npub use serde_json::{Number as PrefabNumber, Value as PrefabValue};\n\n/// An error that can occur while processing a [`Prefab`]\n#[derive(Debug, Clone)]\npub enum PrefabError {\n    CouldNotSerialize(String),\n    CouldNotDeserialize(String),\n}\n\n/// The [`Prefab`] trait is implemented for types that are able to translate to and from\n/// [`PrefabValue`]'s\n///\n/// [`PrefabValue`]'s can then, in turn, be serialized or deserialized for persistance, transfer, or\n/// other purposes.\npub trait Prefab: Serialize + DeserializeOwned {\n    fn from_prefab(data: PrefabValue) -> Result<Self, PrefabError> {\n        match serde_json::from_value(data) {\n            Ok(result) => Ok(result),\n            Err(error) => Err(PrefabError::CouldNotDeserialize(error.to_string())),\n        }\n    }\n\n    fn to_prefab(&self) -> Result<PrefabValue, PrefabError> {\n        match serde_json::to_value(self) {\n            Ok(result) => Ok(result),\n            Err(error) => Err(PrefabError::CouldNotSerialize(error.to_string())),\n        }\n    }\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq)]\npub enum LogKind {\n    Info,\n    Warning,\n    Error,\n}\n\n/// Common logging interface that custom log engines should follow to enable their reusability\n/// across different modules that will log messages to text output targets.\n/// Objects that implement this trait should be considered text output targets, for example text\n/// streams, terminal, network-based loggers, even application screen.\npub trait Logger {\n    /// Log message to this type of text output target.\n    ///\n    /// # Arguments\n    /// * `kind` - Kind of log message.\n    /// * `message` - Message string slice.\n    fn log(&mut self, _kind: LogKind, _message: &str) {}\n}\n\nimpl Logger for () {}\n\n/// Prints log messages to terminal via println! macro.\npub struct PrintLogger;\n\nimpl Logger for PrintLogger {\n    fn log(&mut self, kind: LogKind, message: &str) {\n        println!(\"{kind:?} | {message}\");\n    }\n}\n"
  },
  {
    "path": "crates/core/src/messenger.rs",
    "content": "//! Widget messaging\n\nuse crate::widget::WidgetId;\nuse intuicio_data::type_hash::TypeHash;\nuse std::{any::Any, sync::mpsc::Sender};\n\npub trait MessageData: std::fmt::Debug + Send + Sync {\n    fn clone_message(&self) -> Box<dyn MessageData>;\n    fn as_any(&self) -> &dyn Any;\n\n    fn type_hash(&self) -> TypeHash {\n        TypeHash::of::<Self>()\n    }\n}\n\nimpl Clone for Box<dyn MessageData> {\n    fn clone(&self) -> Self {\n        self.clone_message()\n    }\n}\n\npub type Message = Box<dyn MessageData>;\npub type Messages = Vec<Message>;\n\n#[derive(Clone)]\npub struct MessageSender(Sender<(WidgetId, Message)>);\n\nimpl MessageSender {\n    pub fn new(sender: Sender<(WidgetId, Message)>) -> Self {\n        Self(sender)\n    }\n\n    pub fn write<T>(&self, id: WidgetId, message: T) -> bool\n    where\n        T: 'static + MessageData,\n    {\n        self.0.send((id, Box::new(message))).is_ok()\n    }\n\n    pub fn write_raw(&self, id: WidgetId, message: Message) -> bool {\n        self.0.send((id, message)).is_ok()\n    }\n\n    pub fn write_raw_all<I>(&self, messages: I)\n    where\n        I: IntoIterator<Item = (WidgetId, Message)>,\n    {\n        for data in messages {\n            let _ = self.0.send(data);\n        }\n    }\n}\n\npub struct Messenger<'a> {\n    sender: MessageSender,\n    pub messages: &'a [Message],\n}\n\nimpl<'a> Messenger<'a> {\n    pub fn new(sender: MessageSender, messages: &'a [Message]) -> Self {\n        Self { sender, messages }\n    }\n\n    pub fn write<T>(&self, id: WidgetId, message: T) -> bool\n    where\n        T: 'static + MessageData,\n    {\n        self.sender.write(id, message)\n    }\n\n    pub fn write_raw(&self, id: WidgetId, message: Message) -> bool {\n        self.sender.write_raw(id, message)\n    }\n\n    pub fn write_raw_all<I>(&self, messages: I)\n    where\n        I: IntoIterator<Item = (WidgetId, Message)>,\n    {\n        self.sender.write_raw_all(messages);\n    }\n}\n\n/// Macro for implementing [`MessageData`].\n///\n/// You may prefer to use the [derive macro][`macro@crate::MessageData`] instead, but in case of\n/// auto-implementing MessageData for remote or std types, this might be the macro you find useful.\n#[macro_export]\nmacro_rules! implement_message_data {\n    ($type_name:ty) => {\n        impl $crate::messenger::MessageData for $type_name\n        where\n            Self: Clone,\n        {\n            fn clone_message(&self) -> Box<dyn $crate::messenger::MessageData> {\n                Box::new(self.clone())\n            }\n\n            fn as_any(&self) -> &dyn Any {\n                self\n            }\n        }\n    };\n}\n\nimplement_message_data!(());\nimplement_message_data!(i8);\nimplement_message_data!(i16);\nimplement_message_data!(i32);\nimplement_message_data!(i64);\nimplement_message_data!(i128);\nimplement_message_data!(u8);\nimplement_message_data!(u16);\nimplement_message_data!(u32);\nimplement_message_data!(u64);\nimplement_message_data!(u128);\nimplement_message_data!(f32);\nimplement_message_data!(f64);\nimplement_message_data!(bool);\nimplement_message_data!(String);\n"
  },
  {
    "path": "crates/core/src/props.rs",
    "content": "//! Widget property types\n\nuse crate::{Prefab, PrefabError, PrefabValue};\nuse intuicio_data::type_hash::TypeHash;\nuse serde::{Deserialize, Serialize};\nuse std::{\n    any::{Any, type_name},\n    collections::HashMap,\n};\n\ntype PropsSerializeFactory =\n    Box<dyn Fn(&dyn PropsData) -> Result<PrefabValue, PrefabError> + Send + Sync>;\ntype PropsDeserializeFactory =\n    Box<dyn Fn(PrefabValue, &mut Props) -> Result<(), PrefabError> + Send + Sync>;\n\n#[derive(Default)]\npub struct PropsRegistry {\n    type_mapping: HashMap<TypeHash, String>,\n    factories: HashMap<String, (PropsSerializeFactory, PropsDeserializeFactory)>,\n}\n\nimpl PropsRegistry {\n    pub fn register_factory<T>(&mut self, name: &str)\n    where\n        T: 'static + Prefab + PropsData,\n    {\n        let s: PropsSerializeFactory = Box::new(move |data| {\n            if let Some(data) = data.as_any().downcast_ref::<T>() {\n                data.to_prefab()\n            } else {\n                Err(PrefabError::CouldNotSerialize(\n                    \"Could not downcast to concrete type!\".to_owned(),\n                ))\n            }\n        });\n        let d: PropsDeserializeFactory = Box::new(move |data, props| {\n            props.write(T::from_prefab(data)?);\n            Ok(())\n        });\n        self.factories.insert(name.to_owned(), (s, d));\n        self.type_mapping\n            .insert(TypeHash::of::<T>(), name.to_owned());\n    }\n\n    pub fn unregister_factory(&mut self, name: &str) {\n        self.factories.remove(name);\n    }\n\n    pub fn serialize(&self, props: &Props) -> Result<PrefabValue, PrefabError> {\n        let mut group = PropsGroupPrefab::default();\n        for (t, p) in &props.0 {\n            if let Some(name) = self.type_mapping.get(t) {\n                if let Some(factory) = self.factories.get(name) {\n                    group.data.insert(name.to_owned(), (factory.0)(p.as_ref())?);\n                }\n            } else {\n                return Err(PrefabError::CouldNotSerialize(\n                    \"No type mapping found!\".to_owned(),\n                ));\n            }\n        }\n        group.to_prefab()\n    }\n\n    pub fn deserialize(&self, data: PrefabValue) -> Result<Props, PrefabError> {\n        let data = if data.is_null() {\n            PropsGroupPrefab::default()\n        } else {\n            PropsGroupPrefab::from_prefab(data)?\n        };\n        let mut props = Props::default();\n        for (key, value) in data.data {\n            if let Some(factory) = self.factories.get(&key) {\n                (factory.1)(value, &mut props)?;\n            } else {\n                return Err(PrefabError::CouldNotDeserialize(format!(\n                    \"Could not find properties factory: {key:?}\"\n                )));\n            }\n        }\n        Ok(props)\n    }\n}\n\n#[derive(Debug, Clone)]\npub enum PropsError {\n    CouldNotReadData,\n    HasNoDataOfType(String),\n}\n\nimpl Prefab for PrefabValue {}\n\nimpl PropsData for PrefabValue\nwhere\n    Self: Clone,\n{\n    fn clone_props(&self) -> Box<dyn PropsData> {\n        Box::new(self.clone())\n    }\n\n    fn as_any(&self) -> &dyn Any {\n        self\n    }\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct PropsGroupPrefab {\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"HashMap::is_empty\")]\n    pub data: HashMap<String, PrefabValue>,\n}\n\nimpl Prefab for PropsGroupPrefab {}\n\nimpl PropsData for PropsGroupPrefab\nwhere\n    Self: Clone,\n{\n    fn clone_props(&self) -> Box<dyn PropsData> {\n        Box::new(self.clone())\n    }\n\n    fn as_any(&self) -> &dyn Any {\n        self\n    }\n}\n\npub trait PropsData: Any + std::fmt::Debug + Send + Sync {\n    fn clone_props(&self) -> Box<dyn PropsData>;\n    fn as_any(&self) -> &dyn Any;\n\n    fn type_hash(&self) -> TypeHash {\n        TypeHash::of::<Self>()\n    }\n}\n\nimpl Clone for Box<dyn PropsData> {\n    fn clone(&self) -> Self {\n        self.clone_props()\n    }\n}\n\n#[derive(Default, Clone)]\npub struct Props(HashMap<TypeHash, Box<dyn PropsData>>);\n\nimpl Props {\n    pub fn new<T>(data: T) -> Self\n    where\n        T: 'static + PropsData,\n    {\n        let mut result = HashMap::with_capacity(1);\n        result.insert(TypeHash::of::<T>(), Box::new(data) as Box<dyn PropsData>);\n        Self(result)\n    }\n\n    pub fn is_empty(&self) -> bool {\n        self.0.is_empty()\n    }\n\n    pub fn has<T>(&self) -> bool\n    where\n        T: 'static + PropsData,\n    {\n        let e = TypeHash::of::<T>();\n        self.0.iter().any(|(t, _)| *t == e)\n    }\n\n    pub fn remove<T>(&mut self)\n    where\n        T: 'static + PropsData,\n    {\n        self.0.remove(&TypeHash::of::<T>());\n    }\n\n    pub(crate) unsafe fn remove_by_type(&mut self, id: TypeHash) {\n        self.0.remove(&id);\n    }\n\n    pub fn consume<T>(&mut self) -> Result<Box<dyn PropsData>, PropsError>\n    where\n        T: 'static + PropsData,\n    {\n        if let Some(v) = self.0.remove(&TypeHash::of::<T>()) {\n            Ok(v)\n        } else {\n            Err(PropsError::HasNoDataOfType(type_name::<T>().to_owned()))\n        }\n    }\n\n    pub fn consume_unwrap_cloned<T>(&mut self) -> Result<T, PropsError>\n    where\n        T: 'static + PropsData + Clone,\n    {\n        if let Some(data) = self.consume::<T>()?.as_any().downcast_ref::<T>() {\n            Ok(data.clone())\n        } else {\n            Err(PropsError::CouldNotReadData)\n        }\n    }\n\n    pub fn read<T>(&self) -> Result<&T, PropsError>\n    where\n        T: 'static + PropsData,\n    {\n        let e = TypeHash::of::<T>();\n        if let Some((_, v)) = self.0.iter().find(|(t, _)| **t == e) {\n            if let Some(data) = v.as_any().downcast_ref::<T>() {\n                Ok(data)\n            } else {\n                Err(PropsError::CouldNotReadData)\n            }\n        } else {\n            Err(PropsError::HasNoDataOfType(type_name::<T>().to_owned()))\n        }\n    }\n\n    pub fn map_or_default<T, R, F>(&self, mut f: F) -> R\n    where\n        T: 'static + PropsData,\n        R: Default,\n        F: FnMut(&T) -> R,\n    {\n        match self.read() {\n            Ok(data) => f(data),\n            Err(_) => R::default(),\n        }\n    }\n\n    pub fn map_or_else<T, R, F, E>(&self, mut f: F, mut e: E) -> R\n    where\n        T: 'static + PropsData,\n        F: FnMut(&T) -> R,\n        E: FnMut() -> R,\n    {\n        match self.read() {\n            Ok(data) => f(data),\n            Err(_) => e(),\n        }\n    }\n\n    pub fn read_cloned<T>(&self) -> Result<T, PropsError>\n    where\n        T: 'static + PropsData + Clone,\n    {\n        self.read::<T>().cloned()\n    }\n\n    pub fn read_cloned_or_default<T>(&self) -> T\n    where\n        T: 'static + PropsData + Clone + Default,\n    {\n        self.read_cloned().unwrap_or_default()\n    }\n\n    pub fn read_cloned_or_else<T, F>(&self, mut f: F) -> T\n    where\n        T: 'static + PropsData + Clone + Default,\n        F: FnMut() -> T,\n    {\n        self.read_cloned().unwrap_or_else(|_| f())\n    }\n\n    pub fn write<T>(&mut self, data: T)\n    where\n        T: 'static + PropsData,\n    {\n        self.0\n            .insert(TypeHash::of::<T>(), Box::new(data) as Box<dyn PropsData>);\n    }\n\n    pub fn mutate<T, F>(&mut self, mut f: F)\n    where\n        T: 'static + PropsData,\n        F: FnMut(&T) -> T,\n    {\n        if let Ok(data) = self.read() {\n            let data = f(data);\n            self.write(data);\n        }\n    }\n\n    pub fn mutate_cloned<T, F>(&mut self, mut f: F)\n    where\n        T: 'static + PropsData + Clone,\n        F: FnMut(&mut T),\n    {\n        if let Ok(data) = self.read::<T>() {\n            let mut data = data.clone();\n            f(&mut data);\n            self.write(data);\n        }\n    }\n\n    pub fn mutate_or_write<T, F, W>(&mut self, mut f: F, mut w: W)\n    where\n        T: 'static + PropsData,\n        F: FnMut(&T) -> T,\n        W: FnMut() -> T,\n    {\n        if let Ok(data) = self.read() {\n            let data = f(data);\n            self.write(data);\n        } else {\n            let data = w();\n            self.write(data);\n        }\n    }\n\n    pub fn with<T>(mut self, data: T) -> Self\n    where\n        T: 'static + PropsData,\n    {\n        self.write(data);\n        self\n    }\n\n    pub fn without<T>(mut self) -> Self\n    where\n        T: 'static + PropsData,\n    {\n        self.0.remove(&TypeHash::of::<T>());\n        self\n    }\n\n    pub fn merge(self, other: Self) -> Self {\n        let mut result = self.into_inner();\n        result.extend(other.into_inner());\n        Self(result)\n    }\n\n    pub fn merge_from(&mut self, other: Self) {\n        self.0.extend(other.into_inner());\n    }\n\n    pub(crate) fn into_inner(self) -> HashMap<TypeHash, Box<dyn PropsData>> {\n        self.0\n    }\n}\n\nimpl std::fmt::Debug for Props {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.write_str(\"Props \")?;\n        f.debug_set().entries(self.0.values()).finish()\n    }\n}\n\nimpl<T> From<T> for Props\nwhere\n    T: 'static + PropsData,\n{\n    fn from(data: T) -> Self {\n        Self::new(data)\n    }\n}\n\nimpl From<&Self> for Props {\n    fn from(data: &Self) -> Self {\n        data.clone()\n    }\n}\n\n/// Macro for implementing [`PropsData`].\n///\n/// You may prefer to use the [derive macro][`macro@crate::PropsData`] instead, but in case of\n/// auto-implementing PropsData and Prefab traits for remote or std types, you might find this macro\n/// useful.\n#[macro_export]\nmacro_rules! implement_props_data {\n    ($type_name:ty) => {\n        impl $crate::props::PropsData for $type_name\n        where\n            Self: Clone,\n        {\n            fn clone_props(&self) -> Box<dyn $crate::props::PropsData> {\n                Box::new(self.clone())\n            }\n\n            fn as_any(&self) -> &dyn std::any::Any {\n                self\n            }\n        }\n\n        impl $crate::Prefab for $type_name {}\n    };\n}\n\nimplement_props_data!(());\nimplement_props_data!(i8);\nimplement_props_data!(i16);\nimplement_props_data!(i32);\nimplement_props_data!(i64);\nimplement_props_data!(i128);\nimplement_props_data!(u8);\nimplement_props_data!(u16);\nimplement_props_data!(u32);\nimplement_props_data!(u64);\nimplement_props_data!(u128);\nimplement_props_data!(f32);\nimplement_props_data!(f64);\nimplement_props_data!(isize);\nimplement_props_data!(usize);\nimplement_props_data!(bool);\nimplement_props_data!(String);\n\nmacro_rules! impl_tuple_props_conversion {\n    ($($id:ident),+) => {\n        #[allow(non_snake_case)]\n        impl<$($id: $crate::props::PropsData),+> From<($($id,)+)> for $crate::props::Props {\n            fn from(($($id,)+): ($($id,)+)) -> $crate::props::Props {\n                let mut result = std::collections::HashMap::default();\n                $(\n                    result.insert(\n                        $crate::TypeHash::of::<$id>(),\n                        Box::new($id) as Box<dyn $crate::props::PropsData>,\n                    );\n                )+\n                Self(result)\n            }\n        }\n\n        #[allow(non_snake_case)]\n        impl<$($id: $crate::props::PropsData + Clone + Default),+> From<&$crate::props::Props> for ($($id,)+) {\n            fn from(props: &$crate::props::Props) -> ($($id,)+) {\n                ( $( props.read_cloned_or_default::<$id>(), )+ )\n            }\n        }\n    };\n}\n\nimpl_tuple_props_conversion!(A);\nimpl_tuple_props_conversion!(A, B);\nimpl_tuple_props_conversion!(A, B, C);\nimpl_tuple_props_conversion!(A, B, C, D);\nimpl_tuple_props_conversion!(A, B, C, D, E);\nimpl_tuple_props_conversion!(A, B, C, D, E, F);\nimpl_tuple_props_conversion!(A, B, C, D, E, F, G);\nimpl_tuple_props_conversion!(A, B, C, D, E, F, G, H);\nimpl_tuple_props_conversion!(A, B, C, D, E, F, G, H, I);\nimpl_tuple_props_conversion!(A, B, C, D, E, F, G, H, I, J);\nimpl_tuple_props_conversion!(A, B, C, D, E, F, G, H, I, J, K);\nimpl_tuple_props_conversion!(A, B, C, D, E, F, G, H, I, J, K, L);\nimpl_tuple_props_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M);\nimpl_tuple_props_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);\nimpl_tuple_props_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);\nimpl_tuple_props_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);\nimpl_tuple_props_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q);\nimpl_tuple_props_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R);\nimpl_tuple_props_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S);\nimpl_tuple_props_conversion!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T);\nimpl_tuple_props_conversion!(\n    A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U\n);\nimpl_tuple_props_conversion!(\n    A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V\n);\nimpl_tuple_props_conversion!(\n    A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, X\n);\nimpl_tuple_props_conversion!(\n    A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, X, Y\n);\nimpl_tuple_props_conversion!(\n    A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, X, Y, Z\n);\n"
  },
  {
    "path": "crates/core/src/renderer.rs",
    "content": "//! Renderer traits\n\nuse crate::{\n    layout::{CoordsMapping, Layout},\n    widget::unit::WidgetUnit,\n};\n\npub trait Renderer<T, E> {\n    fn render(\n        &mut self,\n        tree: &WidgetUnit,\n        mapping: &CoordsMapping,\n        layout: &Layout,\n    ) -> Result<T, E>;\n}\n\n#[derive(Debug, Default, Copy, Clone)]\npub struct RawRenderer;\n\nimpl Renderer<WidgetUnit, ()> for RawRenderer {\n    fn render(\n        &mut self,\n        tree: &WidgetUnit,\n        _: &CoordsMapping,\n        _: &Layout,\n    ) -> Result<WidgetUnit, ()> {\n        Ok(tree.clone())\n    }\n}\n"
  },
  {
    "path": "crates/core/src/signals.rs",
    "content": "//! Widget signals\n//!\n//! Signals are a way for widgets to send [messages][crate::messenger] to the RAUI\n//! [`Application`][crate::application::Application]. This can be used to create custom integrations\n//! with the RAUI host or rendering backend.\n//!\n//! Signals may be sent using the [`SignalSender`] in the widget [change context][change_context] or\n//! [unmount context][unmount_context].\n//!\n//! [change_context]: crate::widget::context::WidgetMountOrChangeContext\n//!\n//! [unmount_context]: crate::widget::context::WidgetUnmountContext\n\nuse crate::{\n    messenger::{Message, MessageData},\n    widget::WidgetId,\n};\nuse std::sync::mpsc::Sender;\n\n/// A signal is a [message][crate::messenger] sent by a widget that can be read by the\n/// [`Application`][crate::application::Application]\npub type Signal = (WidgetId, Box<dyn MessageData>);\n\n/// Used to send [`Signal`]s from a component [change context][change_context]\n///\n/// [change_context]: crate::widget::context::WidgetMountOrChangeContext\n#[derive(Clone)]\npub struct SignalSender {\n    id: WidgetId,\n    sender: Sender<Signal>,\n}\n\nimpl SignalSender {\n    /// Create a new [`SignalSender`]\n    pub(crate) fn new(id: WidgetId, sender: Sender<Signal>) -> Self {\n        Self { id, sender }\n    }\n\n    /// Send a message\n    ///\n    /// Returns `false` if the message could not successfully be sent\n    pub fn write<T>(&self, message: T) -> bool\n    where\n        T: 'static + MessageData,\n    {\n        self.sender\n            .send((self.id.clone(), Box::new(message)))\n            .is_ok()\n    }\n\n    /// Send a raw [`Message`]\n    ///\n    /// Returns `false` if the message could not be successfully sent\n    pub fn write_raw(&self, message: Message) -> bool {\n        self.sender.send((self.id.clone(), message)).is_ok()\n    }\n\n    /// Sends a set of raw [`Message`]s from an iterator\n    pub fn write_raw_all<I>(&self, messages: I)\n    where\n        I: IntoIterator<Item = Message>,\n    {\n        for data in messages {\n            let _ = self.sender.send((self.id.clone(), data));\n        }\n    }\n}\n"
  },
  {
    "path": "crates/core/src/state.rs",
    "content": "//! Widget state types\n\nuse crate::props::{Props, PropsData, PropsError};\nuse intuicio_data::type_hash::TypeHash;\nuse std::sync::mpsc::Sender;\n\n#[derive(Debug, Clone)]\npub enum StateError {\n    Props(PropsError),\n    CouldNotWriteChange,\n}\n\n#[derive(Debug, Clone)]\npub enum StateChange {\n    Set(Props),\n    Include(Props),\n    Exclude(TypeHash),\n}\n\n#[derive(Clone)]\npub struct StateUpdate(Sender<StateChange>);\n\nimpl StateUpdate {\n    pub fn new(sender: Sender<StateChange>) -> Self {\n        Self(sender)\n    }\n\n    pub fn set<T>(&self, data: T) -> Result<(), StateError>\n    where\n        T: Into<Props>,\n    {\n        if self.0.send(StateChange::Set(data.into())).is_err() {\n            Err(StateError::CouldNotWriteChange)\n        } else {\n            Ok(())\n        }\n    }\n\n    pub fn include<T>(&self, data: T) -> Result<(), StateError>\n    where\n        T: Into<Props>,\n    {\n        let data = data.into();\n        if self.0.send(StateChange::Include(data)).is_err() {\n            Err(StateError::CouldNotWriteChange)\n        } else {\n            Ok(())\n        }\n    }\n\n    pub fn exclude<T>(&self) -> Result<(), StateError>\n    where\n        T: 'static + PropsData,\n    {\n        if self\n            .0\n            .send(StateChange::Exclude(TypeHash::of::<T>()))\n            .is_err()\n        {\n            Err(StateError::CouldNotWriteChange)\n        } else {\n            Ok(())\n        }\n    }\n}\n\npub struct State<'a> {\n    data: &'a Props,\n    update: StateUpdate,\n}\n\nimpl<'a> State<'a> {\n    pub fn new(data: &'a Props, update: StateUpdate) -> Self {\n        Self { data, update }\n    }\n\n    #[inline]\n    pub fn data(&self) -> &Props {\n        self.data\n    }\n\n    pub fn has<T>(&self) -> bool\n    where\n        T: 'static + PropsData,\n    {\n        self.data.has::<T>()\n    }\n\n    pub fn read<T>(&self) -> Result<&'a T, StateError>\n    where\n        T: 'static + PropsData,\n    {\n        match self.data.read() {\n            Ok(v) => Ok(v),\n            Err(e) => Err(StateError::Props(e)),\n        }\n    }\n\n    pub fn map_or_default<T, R, F>(&self, f: F) -> R\n    where\n        T: 'static + PropsData,\n        R: Default,\n        F: FnMut(&T) -> R,\n    {\n        self.data.map_or_default(f)\n    }\n\n    pub fn map_or_else<T, R, F, E>(&self, f: F, e: E) -> R\n    where\n        T: 'static + PropsData,\n        F: FnMut(&T) -> R,\n        E: FnMut() -> R,\n    {\n        self.data.map_or_else(f, e)\n    }\n\n    pub fn read_cloned<T>(&self) -> Result<T, StateError>\n    where\n        T: 'static + PropsData + Clone,\n    {\n        match self.data.read_cloned() {\n            Ok(v) => Ok(v),\n            Err(e) => Err(StateError::Props(e)),\n        }\n    }\n\n    pub fn read_cloned_or_default<T>(&self) -> T\n    where\n        T: 'static + PropsData + Clone + Default,\n    {\n        self.data.read_cloned_or_default()\n    }\n\n    pub fn read_cloned_or_else<T, F>(&self, f: F) -> T\n    where\n        T: 'static + PropsData + Clone + Default,\n        F: FnMut() -> T,\n    {\n        self.data.read_cloned_or_else(f)\n    }\n\n    pub fn write<T>(&self, data: T) -> Result<(), StateError>\n    where\n        T: 'static + PropsData + Send + Sync,\n    {\n        self.update.set(data)\n    }\n\n    pub fn write_with<T>(&self, data: T) -> Result<(), StateError>\n    where\n        T: 'static + PropsData + Send + Sync,\n    {\n        self.update.include(data)\n    }\n\n    pub fn write_without<T>(&self) -> Result<(), StateError>\n    where\n        T: 'static + PropsData + Send + Sync,\n    {\n        self.update.exclude::<T>()\n    }\n\n    pub fn mutate<T, F>(&self, mut f: F) -> Result<(), StateError>\n    where\n        T: 'static + PropsData + Send + Sync,\n        F: FnMut(&T) -> T,\n    {\n        match self.read() {\n            Ok(data) => {\n                let data = f(data);\n                self.write(data)\n            }\n            Err(error) => Err(error),\n        }\n    }\n\n    pub fn mutate_cloned<T, F>(&self, mut f: F) -> Result<(), StateError>\n    where\n        T: 'static + PropsData + Send + Sync + Clone,\n        F: FnMut(&mut T),\n    {\n        match self.read::<T>() {\n            Ok(data) => {\n                let mut data = data.clone();\n                f(&mut data);\n                self.write(data)\n            }\n            Err(error) => Err(error),\n        }\n    }\n\n    pub fn update(&self) -> &StateUpdate {\n        &self.update\n    }\n}\n"
  },
  {
    "path": "crates/core/src/tester.rs",
    "content": "use crate::{\n    application::Application,\n    interactive::default_interactions_engine::DefaultInteractionsEngine,\n    layout::{CoordsMapping, default_layout_engine::DefaultLayoutEngine},\n};\n\npub trait AppCycleFrameRunner<T> {\n    fn run_frame(self, tester: &mut AppCycleTester<T>);\n}\n\nimpl<T> AppCycleFrameRunner<T> for () {\n    fn run_frame(self, _: &mut AppCycleTester<T>) {}\n}\n\nimpl<T, F> AppCycleFrameRunner<T> for F\nwhere\n    F: FnMut(&mut AppCycleTester<T>),\n{\n    fn run_frame(mut self, tester: &mut AppCycleTester<T>) {\n        (self)(tester);\n    }\n}\n\npub struct AppCycleTester<T> {\n    pub coords_mapping: CoordsMapping,\n    pub application: Application,\n    pub layout_engine: DefaultLayoutEngine,\n    pub interactions_engine: DefaultInteractionsEngine,\n    pub user_data: T,\n}\n\nimpl<T> AppCycleTester<T> {\n    pub fn new(coords_mapping: CoordsMapping, user_data: T) -> Self {\n        Self {\n            coords_mapping,\n            application: Default::default(),\n            layout_engine: Default::default(),\n            interactions_engine: Default::default(),\n            user_data,\n        }\n    }\n\n    pub fn run_frame(&mut self, frame_runner: impl AppCycleFrameRunner<T>) {\n        frame_runner.run_frame(self);\n        if self.application.process() {\n            self.application\n                .layout(&self.coords_mapping, &mut self.layout_engine)\n                .unwrap();\n        }\n        self.application\n            .interact(&mut self.interactions_engine)\n            .unwrap();\n    }\n}\n"
  },
  {
    "path": "crates/core/src/view_model.rs",
    "content": "use crate::widget::{WidgetId, WidgetIdCommon};\nuse intuicio_data::{\n    lifetime::{ValueReadAccess, ValueWriteAccess},\n    managed::DynamicManaged,\n    managed::{Managed, ManagedLazy, ManagedRef, ManagedRefMut},\n};\nuse std::{\n    collections::{HashMap, HashSet},\n    ops::{Deref, DerefMut},\n};\n\npub struct ViewModelBindings {\n    widgets: HashSet<WidgetId>,\n    common_root: WidgetIdCommon,\n    notify: bool,\n}\n\nimpl Default for ViewModelBindings {\n    fn default() -> Self {\n        Self {\n            widgets: Default::default(),\n            common_root: Default::default(),\n            notify: true,\n        }\n    }\n}\n\nimpl ViewModelBindings {\n    pub fn bind(&mut self, id: WidgetId) {\n        self.widgets.insert(id);\n        self.rebuild_common_root();\n    }\n\n    pub fn unbind(&mut self, id: &WidgetId) {\n        self.widgets.remove(id);\n        self.rebuild_common_root();\n    }\n\n    pub fn clear(&mut self) {\n        self.widgets.clear();\n        self.common_root = Default::default();\n    }\n\n    pub fn is_empty(&self) -> bool {\n        self.widgets.is_empty()\n    }\n\n    pub fn is_bound(&self, id: &WidgetId) -> bool {\n        self.widgets.contains(id)\n    }\n\n    pub fn widgets(&self) -> impl Iterator<Item = &WidgetId> {\n        self.widgets.iter()\n    }\n\n    pub fn common_root(&self) -> &WidgetIdCommon {\n        &self.common_root\n    }\n\n    pub fn notify(&mut self) {\n        self.notify = true;\n    }\n\n    pub fn is_notified(&self) -> bool {\n        self.notify\n    }\n\n    pub fn consume_notification(&mut self) -> bool {\n        !self.widgets.is_empty() && std::mem::take(&mut self.notify)\n    }\n\n    fn rebuild_common_root(&mut self) {\n        self.common_root = WidgetIdCommon::from_iter(self.widgets.iter());\n    }\n}\n\n#[derive(Default)]\npub struct ViewModelProperties {\n    inner: HashMap<String, Managed<ViewModelBindings>>,\n}\n\nimpl ViewModelProperties {\n    pub fn unbind_all(&mut self, id: &WidgetId) {\n        for bindings in self.inner.values_mut() {\n            if let Some(mut bindings) = bindings.write() {\n                bindings.unbind(id);\n            }\n        }\n    }\n\n    pub fn remove(&mut self, id: &str) {\n        self.inner.remove(id);\n    }\n\n    pub fn clear(&mut self) {\n        self.inner.clear();\n    }\n\n    pub fn is_empty(&self) -> bool {\n        self.inner.is_empty()\n    }\n\n    pub fn has(&self, id: &str) -> bool {\n        self.inner.contains_key(id)\n    }\n\n    pub fn remove_empty_bindings(&mut self) {\n        let to_remove = self\n            .inner\n            .iter()\n            .filter_map(|(key, bindings)| {\n                if let Some(bindings) = bindings.read()\n                    && bindings.is_empty()\n                {\n                    return Some(key.to_owned());\n                }\n                None\n            })\n            .collect::<Vec<_>>();\n        for key in to_remove {\n            self.inner.remove(&key);\n        }\n    }\n\n    pub fn bindings(\n        &'_ mut self,\n        id: impl ToString,\n    ) -> Option<ValueWriteAccess<'_, ViewModelBindings>> {\n        self.inner.entry(id.to_string()).or_default().write()\n    }\n\n    pub fn notifier(&mut self, id: impl ToString) -> ViewModelNotifier {\n        ViewModelNotifier {\n            inner: self.inner.entry(id.to_string()).or_default().lazy(),\n        }\n    }\n\n    pub fn consume_notification(&mut self) -> bool {\n        self.inner.values_mut().any(|bindings| {\n            bindings\n                .write()\n                .map(|mut bindings| bindings.consume_notification())\n                .unwrap_or_default()\n        })\n    }\n\n    pub fn consume_notified_common_root(&mut self) -> WidgetIdCommon {\n        let mut result = WidgetIdCommon::default();\n        for bindings in self.inner.values_mut() {\n            if let Some(mut bindings) = bindings.write()\n                && bindings.consume_notification()\n            {\n                let root = bindings.common_root();\n                result.include_other(root);\n            }\n        }\n        result\n    }\n}\n\n#[derive(Clone)]\npub struct ViewModelNotifier {\n    inner: ManagedLazy<ViewModelBindings>,\n}\n\nimpl ViewModelNotifier {\n    pub fn notify(&mut self) -> bool {\n        if let Some(mut bindings) = self.inner.write() {\n            bindings.notify();\n            true\n        } else {\n            false\n        }\n    }\n}\n\npub struct ViewModel {\n    object: DynamicManaged,\n    pub properties: ViewModelProperties,\n}\n\nimpl ViewModel {\n    pub fn new<T: 'static>(object: T, properties: ViewModelProperties) -> Self {\n        Self {\n            object: DynamicManaged::new(object).ok().unwrap(),\n            properties,\n        }\n    }\n\n    pub fn new_object<T: 'static>(object: T) -> Self {\n        Self::new(object, Default::default())\n    }\n\n    pub fn produce<T: 'static>(producer: impl FnOnce(&mut ViewModelProperties) -> T) -> Self {\n        let mut properties = Default::default();\n        let object = DynamicManaged::new(producer(&mut properties)).ok().unwrap();\n        Self { object, properties }\n    }\n\n    pub fn borrow<T: 'static>(&self) -> Option<ManagedRef<T>> {\n        self.object\n            .borrow()\n            .and_then(|object| object.into_typed::<T>().ok())\n    }\n\n    pub fn borrow_mut<T: 'static>(&mut self) -> Option<ManagedRefMut<T>> {\n        self.object\n            .borrow_mut()\n            .and_then(|object| object.into_typed::<T>().ok())\n    }\n\n    pub fn lazy<T: 'static>(&self) -> Option<ManagedLazy<T>> {\n        self.object.lazy().into_typed::<T>().ok()\n    }\n\n    pub fn read<T: 'static>(&'_ self) -> Option<ValueReadAccess<'_, T>> {\n        self.object.read::<T>()\n    }\n\n    pub fn write<T: 'static>(&'_ mut self) -> Option<ValueWriteAccess<'_, T>> {\n        self.object.write::<T>()\n    }\n\n    pub fn write_notified<T: 'static>(&'_ mut self) -> Option<ViewModelObject<'_, T>> {\n        if let Some(access) = self.object.write::<T>() {\n            Some(ViewModelObject {\n                access,\n                notifier: self.properties.notifier(\"\"),\n            })\n        } else {\n            None\n        }\n    }\n}\n\n#[derive(Default)]\npub struct ViewModelCollection {\n    named: HashMap<String, ViewModel>,\n    widgets: HashMap<WidgetId, HashMap<String, ViewModel>>,\n}\n\nimpl ViewModelCollection {\n    pub fn unbind_all(&mut self, id: &WidgetId) {\n        for view_model in self.named.values_mut() {\n            view_model.properties.unbind_all(id);\n        }\n        for view_model in self.widgets.values_mut() {\n            for view_model in view_model.values_mut() {\n                view_model.properties.unbind_all(id);\n            }\n        }\n    }\n\n    pub fn remove_empty_bindings(&mut self) {\n        for view_model in self.named.values_mut() {\n            view_model.properties.remove_empty_bindings();\n        }\n        for view_model in self.widgets.values_mut() {\n            for view_model in view_model.values_mut() {\n                view_model.properties.remove_empty_bindings();\n            }\n        }\n    }\n\n    pub fn consume_notification(&mut self) -> bool {\n        let mut result = false;\n        for view_model in self.named.values_mut() {\n            result = result || view_model.properties.consume_notification();\n        }\n        for view_model in self.widgets.values_mut() {\n            for view_model in view_model.values_mut() {\n                result = result || view_model.properties.consume_notification();\n            }\n        }\n        result\n    }\n\n    pub fn consume_notified_common_root(&mut self) -> WidgetIdCommon {\n        let mut result = WidgetIdCommon::default();\n        for view_model in self.named.values_mut() {\n            result.include_other(&view_model.properties.consume_notified_common_root());\n        }\n        for view_model in self.widgets.values_mut() {\n            for view_model in view_model.values_mut() {\n                result.include_other(&view_model.properties.consume_notified_common_root());\n            }\n        }\n        result\n    }\n\n    pub fn remove_widget_view_models(&mut self, id: &WidgetId) {\n        self.widgets.remove(id);\n    }\n}\n\nimpl Deref for ViewModelCollection {\n    type Target = HashMap<String, ViewModel>;\n\n    fn deref(&self) -> &Self::Target {\n        &self.named\n    }\n}\n\nimpl DerefMut for ViewModelCollection {\n    fn deref_mut(&mut self) -> &mut Self::Target {\n        &mut self.named\n    }\n}\n\npub struct ViewModelCollectionView<'a> {\n    id: &'a WidgetId,\n    collection: &'a mut ViewModelCollection,\n}\n\nimpl<'a> ViewModelCollectionView<'a> {\n    pub fn new(id: &'a WidgetId, collection: &'a mut ViewModelCollection) -> Self {\n        Self { id, collection }\n    }\n\n    pub fn id(&self) -> &WidgetId {\n        self.id\n    }\n\n    pub fn collection(&'a self) -> &'a ViewModelCollection {\n        self.collection\n    }\n\n    pub fn collection_mut(&'a mut self) -> &'a mut ViewModelCollection {\n        self.collection\n    }\n\n    pub fn bindings(\n        &'_ mut self,\n        view_model: &str,\n        property: impl ToString,\n    ) -> Option<ValueWriteAccess<'_, ViewModelBindings>> {\n        self.collection\n            .get_mut(view_model)?\n            .properties\n            .bindings(property)\n    }\n\n    pub fn view_model(&self, name: &str) -> Option<&ViewModel> {\n        self.collection.get(name)\n    }\n\n    pub fn view_model_mut(&mut self, name: &str) -> Option<&mut ViewModel> {\n        self.collection.get_mut(name)\n    }\n\n    pub fn widget_register(&mut self, name: impl ToString, view_model: ViewModel) {\n        self.collection\n            .widgets\n            .entry(self.id.to_owned())\n            .or_default()\n            .insert(name.to_string(), view_model);\n    }\n\n    pub fn widget_unregister(&mut self, name: &str) -> Option<ViewModel> {\n        let view_models = self.collection.widgets.get_mut(self.id)?;\n        let result = view_models.remove(name)?;\n        if view_models.is_empty() {\n            self.collection.widgets.remove(self.id);\n        }\n        Some(result)\n    }\n\n    pub fn widget_bindings(\n        &'_ mut self,\n        view_model: &str,\n        property: impl ToString,\n    ) -> Option<ValueWriteAccess<'_, ViewModelBindings>> {\n        self.collection\n            .widgets\n            .get_mut(self.id)?\n            .get_mut(view_model)?\n            .properties\n            .bindings(property)\n    }\n\n    pub fn widget_view_model(&self, name: &str) -> Option<&ViewModel> {\n        self.collection.widgets.get(self.id)?.get(name)\n    }\n\n    pub fn widget_view_model_mut(&mut self, name: &str) -> Option<&mut ViewModel> {\n        self.collection.widgets.get_mut(self.id)?.get_mut(name)\n    }\n\n    pub fn hierarchy_view_model(&self, name: &str) -> Option<&ViewModel> {\n        self.collection\n            .widgets\n            .iter()\n            .filter_map(|(id, view_models)| {\n                id.distance_to(self.id).ok().and_then(|distance| {\n                    if distance <= 0 {\n                        Some((distance, view_models.get(name)?))\n                    } else {\n                        None\n                    }\n                })\n            })\n            .min_by(|(a, _), (b, _)| a.cmp(b))\n            .map(|(_, view_model)| view_model)\n    }\n\n    pub fn hierarchy_view_model_mut(&mut self, name: &str) -> Option<&mut ViewModel> {\n        self.collection\n            .widgets\n            .iter_mut()\n            .filter_map(|(id, view_models)| {\n                id.distance_to(self.id).ok().and_then(|distance| {\n                    if distance <= 0 {\n                        Some((distance, view_models.get_mut(name)?))\n                    } else {\n                        None\n                    }\n                })\n            })\n            .min_by(|(a, _), (b, _)| a.cmp(b))\n            .map(|(_, view_model)| view_model)\n    }\n}\n\npub struct ViewModelObject<'a, T> {\n    access: ValueWriteAccess<'a, T>,\n    notifier: ViewModelNotifier,\n}\n\nimpl<T> ViewModelObject<'_, T> {\n    pub fn set_unique_notify(&mut self, value: T)\n    where\n        T: PartialEq,\n    {\n        if *self.access != value {\n            *self.access = value;\n            self.notifier.notify();\n        }\n    }\n}\n\nimpl<T> Deref for ViewModelObject<'_, T> {\n    type Target = T;\n\n    fn deref(&self) -> &Self::Target {\n        &self.access\n    }\n}\n\nimpl<T> DerefMut for ViewModelObject<'_, T> {\n    fn deref_mut(&mut self) -> &mut Self::Target {\n        self.notifier.notify();\n        &mut self.access\n    }\n}\n\npub struct ViewModelValue<T> {\n    value: T,\n    notifier: ViewModelNotifier,\n}\n\nimpl<T> ViewModelValue<T> {\n    pub fn new(value: T, notifier: ViewModelNotifier) -> Self {\n        Self { value, notifier }\n    }\n\n    pub fn consume(self) -> T {\n        self.value\n    }\n\n    pub fn set_unique_notify(&mut self, value: T)\n    where\n        T: PartialEq,\n    {\n        if self.value != value {\n            self.value = value;\n            self.notifier.notify();\n        }\n    }\n}\n\nimpl<T> Deref for ViewModelValue<T> {\n    type Target = T;\n\n    fn deref(&self) -> &Self::Target {\n        &self.value\n    }\n}\n\nimpl<T> DerefMut for ViewModelValue<T> {\n    fn deref_mut(&mut self) -> &mut Self::Target {\n        self.notifier.notify();\n        &mut self.value\n    }\n}\n\nimpl<T> std::fmt::Display for ViewModelValue<T>\nwhere\n    T: std::fmt::Display,\n{\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{}\", self.value)\n    }\n}\n\nimpl<T> std::fmt::Debug for ViewModelValue<T>\nwhere\n    T: std::fmt::Debug,\n{\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"ViewModelValue\")\n            .field(\"value\", &self.value)\n            .finish()\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use std::str::FromStr;\n\n    const FOO_VIEW_MODEL: &str = \"foo\";\n    const COUNTER_PROPERTY: &str = \"counter\";\n    const FLAG_PROPERTY: &str = \"flag\";\n\n    // view-model data type\n    struct Foo {\n        // can hold view-model value wrapper that implicitly notifies on mutation.\n        counter: ViewModelValue<usize>,\n        // or can hold raw notifiers to explicitly notify.\n        flag: bool,\n        flag_notifier: ViewModelNotifier,\n    }\n\n    impl Foo {\n        fn toggle(&mut self) {\n            self.flag = !self.flag;\n            self.flag_notifier.notify();\n        }\n    }\n\n    #[test]\n    fn test_view_model() {\n        let a = WidgetId::from_str(\"a:root/a\").unwrap();\n        let b = WidgetId::from_str(\"b:root/b\").unwrap();\n        let mut collection = ViewModelCollection::default();\n\n        // create new view-model and add it to collection.\n        // `produce` method allows to setup notifiers as we construct view-model.\n        let view_model = ViewModel::produce(|properties| Foo {\n            counter: ViewModelValue::new(0, properties.notifier(COUNTER_PROPERTY)),\n            flag: false,\n            flag_notifier: properties.notifier(FLAG_PROPERTY),\n        });\n        // handle to view-model data we can use to share around.\n        // it stays alive as long as its view-model object.\n        let handle = view_model.lazy::<Foo>().unwrap();\n        collection.insert(FOO_VIEW_MODEL.to_owned(), view_model);\n\n        // unbound properties won't trigger notification until we bind widgets to them.\n        assert!(!collection.consume_notified_common_root().is_valid());\n        handle.write().unwrap().toggle();\n        assert!(!collection.consume_notified_common_root().is_valid());\n        assert!(\n            collection\n                .get_mut(FOO_VIEW_MODEL)\n                .unwrap()\n                .properties\n                .bindings(COUNTER_PROPERTY)\n                .unwrap()\n                .is_notified()\n        );\n        assert!(\n            collection\n                .get_mut(FOO_VIEW_MODEL)\n                .unwrap()\n                .properties\n                .bindings(FLAG_PROPERTY)\n                .unwrap()\n                .is_notified()\n        );\n\n        // bind widget to properties.\n        // whenever property gets notified, its widgets will rebuild.\n        collection\n            .get_mut(FOO_VIEW_MODEL)\n            .unwrap()\n            .properties\n            .bindings(COUNTER_PROPERTY)\n            .unwrap()\n            .bind(a);\n        collection\n            .get_mut(FOO_VIEW_MODEL)\n            .unwrap()\n            .properties\n            .bindings(FLAG_PROPERTY)\n            .unwrap()\n            .bind(b);\n\n        // once we bind properties, notification will be triggered.\n        assert_eq!(\n            collection.consume_notified_common_root().path(),\n            Some(\"root\")\n        );\n\n        // automatically notify on view-model value mutation.\n        *handle.write().unwrap().counter += 1;\n        assert_eq!(\n            collection.consume_notified_common_root().path(),\n            Some(\"root/a\"),\n        );\n\n        // proxy notify via view-model method call.\n        handle.write().unwrap().toggle();\n        assert_eq!(\n            collection.consume_notified_common_root().path(),\n            Some(\"root/b\"),\n        );\n\n        // rebuilding widgets tree will occur always from common root of notified widgets.\n        *handle.write().unwrap().counter += 1;\n        handle.write().unwrap().toggle();\n        assert_eq!(\n            collection.consume_notified_common_root().path(),\n            Some(\"root\"),\n        );\n    }\n}\n"
  },
  {
    "path": "crates/core/src/widget/component/containers/anchor_box.rs",
    "content": "use crate::{\n    MessageData, PropsData,\n    messenger::MessageData,\n    pre_hooks, unpack_named_slots,\n    widget::{\n        WidgetId, WidgetIdOrRef,\n        component::{RelativeLayoutListenerSignal, use_relative_layout_listener},\n        context::{WidgetContext, WidgetMountOrChangeContext},\n        node::WidgetNode,\n        unit::{area::AreaBoxNode, content::ContentBoxItemLayout},\n        utils::{Rect, Vec2, lerp},\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Default, Copy, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct AnchorProps {\n    #[serde(default)]\n    pub outer_box_size: Vec2,\n    #[serde(default)]\n    pub inner_box_rect: Rect,\n}\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct AnchorNotifyProps(\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"WidgetIdOrRef::is_none\")]\n    pub WidgetIdOrRef,\n);\n\n#[derive(MessageData, Debug, Clone)]\n#[message_data(crate::messenger::MessageData)]\npub struct AnchorNotifyMessage {\n    pub sender: WidgetId,\n    pub state: AnchorProps,\n    pub prev: AnchorProps,\n}\n\n#[derive(PropsData, Debug, Copy, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct PivotBoxProps {\n    #[serde(default = \"PivotBoxProps::default_pivot\")]\n    pub pivot: Vec2,\n    #[serde(default)]\n    pub align: Vec2,\n}\n\nimpl Default for PivotBoxProps {\n    fn default() -> Self {\n        Self {\n            pivot: Self::default_pivot(),\n            align: Default::default(),\n        }\n    }\n}\n\nimpl PivotBoxProps {\n    fn default_pivot() -> Vec2 {\n        Vec2 { x: 0.0, y: 1.0 }\n    }\n}\n\npub fn use_anchor_box_notified_state(context: &mut WidgetContext) {\n    context.life_cycle.change(|context| {\n        for msg in context.messenger.messages {\n            if let Some(msg) = msg.as_any().downcast_ref::<AnchorNotifyMessage>() {\n                let _ = context.state.write_with(msg.state);\n            }\n        }\n    });\n}\n\n#[pre_hooks(use_relative_layout_listener)]\npub fn use_anchor_box(context: &mut WidgetContext) {\n    fn notify<T>(context: &WidgetMountOrChangeContext, data: T)\n    where\n        T: 'static + MessageData,\n    {\n        if let Ok(AnchorNotifyProps(notify)) = context.props.read()\n            && let Some(to) = notify.read()\n        {\n            context.messenger.write(to, data);\n        }\n    }\n\n    context.life_cycle.mount(|context| {\n        notify(\n            &context,\n            AnchorNotifyMessage {\n                sender: context.id.to_owned(),\n                state: Default::default(),\n                prev: Default::default(),\n            },\n        );\n        let _ = context.state.write_with(AnchorProps::default());\n    });\n\n    context.life_cycle.change(|context| {\n        let mut data = context.state.read_cloned_or_default::<AnchorProps>();\n        let prev = data;\n        let mut dirty = false;\n        for msg in context.messenger.messages {\n            if let Some(RelativeLayoutListenerSignal::Change(size, rect)) =\n                msg.as_any().downcast_ref()\n            {\n                data.outer_box_size = *size;\n                data.inner_box_rect = *rect;\n                dirty = true;\n            }\n        }\n        if dirty {\n            notify(\n                &context,\n                AnchorNotifyMessage {\n                    sender: context.id.to_owned(),\n                    state: data,\n                    prev,\n                },\n            );\n            let _ = context.state.write_with(data);\n        }\n    });\n}\n\n#[pre_hooks(use_anchor_box)]\npub fn anchor_box(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        id,\n        state,\n        named_slots,\n        ..\n    } = context;\n    unpack_named_slots!(named_slots => content);\n\n    let anchor_props = state.read_cloned_or_default::<AnchorProps>();\n\n    content.remap_props(|props| props.with(anchor_props));\n\n    AreaBoxNode {\n        id: id.to_owned(),\n        slot: Box::new(content),\n    }\n    .into()\n}\n\npub fn pivot_point_to_anchor(pivot: Vec2, anchor: &AnchorProps) -> Vec2 {\n    let x = if anchor.outer_box_size.x > 0.0 {\n        let v = lerp(\n            anchor.inner_box_rect.left,\n            anchor.inner_box_rect.right,\n            pivot.x,\n        );\n        v / anchor.outer_box_size.x\n    } else {\n        0.0\n    };\n    let y = if anchor.outer_box_size.y > 0.0 {\n        let v = lerp(\n            anchor.inner_box_rect.top,\n            anchor.inner_box_rect.bottom,\n            pivot.y,\n        );\n        v / anchor.outer_box_size.y\n    } else {\n        0.0\n    };\n    Vec2 { x, y }\n}\n\n/// (anchor point, align factor)\npub fn pivot_to_anchor_and_align(pivot: &PivotBoxProps, anchor: &AnchorProps) -> (Vec2, Vec2) {\n    (pivot_point_to_anchor(pivot.pivot, anchor), pivot.align)\n}\n\npub fn pivot_box(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        id,\n        props,\n        named_slots,\n        ..\n    } = context;\n    unpack_named_slots!(named_slots => content);\n\n    let anchor_props = props.read_cloned_or_default::<AnchorProps>();\n    let pivot_props = props.read_cloned_or_default::<PivotBoxProps>();\n    let (Vec2 { x, y }, align) = pivot_to_anchor_and_align(&pivot_props, &anchor_props);\n\n    content.remap_props(|content_props| {\n        let mut item_props = content_props.read_cloned_or_default::<ContentBoxItemLayout>();\n        item_props.anchors = Rect {\n            left: x,\n            right: x,\n            top: y,\n            bottom: y,\n        };\n        item_props.align = align;\n        content_props.with(item_props)\n    });\n\n    AreaBoxNode {\n        id: id.to_owned(),\n        slot: Box::new(content),\n    }\n    .into()\n}\n"
  },
  {
    "path": "crates/core/src/widget/component/containers/area_box.rs",
    "content": "use crate::{\n    unpack_named_slots,\n    widget::{context::WidgetContext, node::WidgetNode, unit::area::AreaBoxNode},\n};\n\npub fn area_box(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        id, named_slots, ..\n    } = context;\n    unpack_named_slots!(named_slots => content);\n\n    AreaBoxNode {\n        id: id.to_owned(),\n        slot: Box::new(content),\n    }\n    .into()\n}\n"
  },
  {
    "path": "crates/core/src/widget/component/containers/content_box.rs",
    "content": "//! A generic container for content with optional clipping and transforms\n\nuse crate::{\n    PropsData, make_widget, pre_hooks,\n    widget::{\n        component::interactive::navigation::{\n            NavContainerActive, NavItemActive, NavJumpActive, use_nav_container_active,\n            use_nav_item, use_nav_jump_direction_active,\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::content::{\n            ContentBoxContentReposition, ContentBoxItemLayout, ContentBoxItemNode, ContentBoxNode,\n        },\n        utils::Transform,\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n/// The properties of a [`content_box`] component\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct ContentBoxProps {\n    /// Whether or not to clip the parts of items that overflow outside of the box bounds\n    #[serde(default)]\n    pub clipping: bool,\n    /// The content repositioning strategy to use.\n    pub content_reposition: ContentBoxContentReposition,\n    /// The transform to apply to the box and it's contents\n    #[serde(default)]\n    pub transform: Transform,\n}\n\n#[pre_hooks(use_nav_container_active, use_nav_jump_direction_active, use_nav_item)]\npub fn nav_content_box(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        key,\n        props,\n        listed_slots,\n        ..\n    } = context;\n\n    let props = props\n        .clone()\n        .without::<NavContainerActive>()\n        .without::<NavJumpActive>()\n        .without::<NavItemActive>();\n\n    make_widget!(content_box)\n        .key(key)\n        .merge_props(props)\n        .listed_slots(listed_slots)\n        .into()\n}\n\n/// A generic container for other widgets\n///\n/// [`content_box`]'s serve two basic purposes: allowing you to apply transformations and clipping\n/// to all contained widgets and giving contained widgets more control over their layout inside of\n/// the box.\n///\n/// # Transform & Clipping\n///\n/// The transformation and clipping options on the [`content_box`] can be set by setting the\n/// [`ContentBoxProps`] on the component.\n///\n/// # Child Widget Layout\n///\n/// With a [`content_box`] you can get more control over the layout of it's children by adding the\n/// [`ContentBoxItemLayout`] properties to any of it's children.\npub fn content_box(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        id,\n        props,\n        listed_slots,\n        ..\n    } = context;\n\n    let ContentBoxProps {\n        clipping,\n        transform,\n        content_reposition,\n    } = props.read_cloned_or_default();\n\n    let items = listed_slots\n        .into_iter()\n        .filter_map(|slot| {\n            if let Some(props) = slot.props() {\n                let layout = props.read_cloned_or_default::<ContentBoxItemLayout>();\n                Some(ContentBoxItemNode { slot, layout })\n            } else {\n                None\n            }\n        })\n        .collect::<Vec<_>>();\n\n    ContentBoxNode {\n        id: id.to_owned(),\n        props: props.clone(),\n        items,\n        clipping,\n        content_reposition,\n        transform,\n    }\n    .into()\n}\n"
  },
  {
    "path": "crates/core/src/widget/component/containers/context_box.rs",
    "content": "use crate::{\n    PropsData, make_widget, pre_hooks, unpack_named_slots,\n    widget::{\n        component::containers::{\n            anchor_box::{AnchorProps, PivotBoxProps, pivot_to_anchor_and_align, use_anchor_box},\n            content_box::content_box,\n            portal_box::{portal_box, use_portals_container_relative_layout},\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::{area::AreaBoxNode, content::ContentBoxItemLayout},\n        utils::{Rect, Vec2},\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Default, Copy, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct ContextBoxProps {\n    #[serde(default)]\n    pub show: bool,\n}\n\n#[pre_hooks(use_anchor_box)]\npub fn context_box(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        id,\n        idref,\n        key,\n        props,\n        state,\n        named_slots,\n        ..\n    } = context;\n    unpack_named_slots!(named_slots => {content, context, backdrop});\n\n    let ContextBoxProps { show } = props.read_cloned_or_default();\n    let anchor_state = state.read_cloned_or_default::<AnchorProps>();\n    let pivot_props = props.read_cloned_or_default::<PivotBoxProps>();\n    let (Vec2 { x, y }, align) = pivot_to_anchor_and_align(&pivot_props, &anchor_state);\n\n    let context = if show {\n        context.remap_props(|content_props| {\n            let mut item_props = content_props.read_cloned_or_default::<ContentBoxItemLayout>();\n            item_props.anchors = Rect {\n                left: x,\n                right: x,\n                top: y,\n                bottom: y,\n            };\n            item_props.align = align;\n            content_props.with(item_props)\n        });\n\n        make_widget!(portal_box)\n            .named_slot(\n                \"content\",\n                make_widget!(content_box)\n                    .key(\"content\")\n                    .listed_slot(backdrop)\n                    .listed_slot(context),\n            )\n            .into()\n    } else {\n        WidgetNode::default()\n    };\n\n    let content = make_widget!(content_box)\n        .key(key)\n        .maybe_idref(idref.cloned())\n        .merge_props(props.clone())\n        .listed_slot(content)\n        .listed_slot(context)\n        .into();\n\n    AreaBoxNode {\n        id: id.to_owned(),\n        slot: Box::new(content),\n    }\n    .into()\n}\n\n#[pre_hooks(use_portals_container_relative_layout)]\npub fn portals_context_box(mut context: WidgetContext) -> WidgetNode {\n    context_box(context)\n}\n"
  },
  {
    "path": "crates/core/src/widget/component/containers/flex_box.rs",
    "content": "use crate::{\n    PropsData, Scalar, make_widget, pre_hooks,\n    widget::{\n        component::interactive::navigation::{\n            NavContainerActive, NavItemActive, NavJumpActive, use_nav_container_active,\n            use_nav_item, use_nav_jump,\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::flex::{FlexBoxDirection, FlexBoxItemLayout, FlexBoxItemNode, FlexBoxNode},\n        utils::Transform,\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct FlexBoxProps {\n    #[serde(default)]\n    pub direction: FlexBoxDirection,\n    #[serde(default)]\n    pub separation: Scalar,\n    #[serde(default)]\n    pub wrap: bool,\n    #[serde(default)]\n    pub transform: Transform,\n}\n\n#[pre_hooks(use_nav_container_active, use_nav_jump, use_nav_item)]\npub fn nav_flex_box(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        key,\n        props,\n        listed_slots,\n        ..\n    } = context;\n\n    let props = props\n        .clone()\n        .without::<NavContainerActive>()\n        .without::<NavJumpActive>()\n        .without::<NavItemActive>();\n\n    make_widget!(flex_box)\n        .key(key)\n        .merge_props(props)\n        .listed_slots(listed_slots)\n        .into()\n}\n\npub fn flex_box(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        id,\n        props,\n        listed_slots,\n        ..\n    } = context;\n\n    let FlexBoxProps {\n        direction,\n        separation,\n        wrap,\n        transform,\n    } = props.read_cloned_or_default();\n\n    let items = listed_slots\n        .into_iter()\n        .filter_map(|slot| {\n            if let Some(props) = slot.props() {\n                let layout = props.read_cloned_or_default::<FlexBoxItemLayout>();\n                Some(FlexBoxItemNode { slot, layout })\n            } else {\n                None\n            }\n        })\n        .collect::<Vec<_>>();\n\n    FlexBoxNode {\n        id: id.to_owned(),\n        props: props.clone(),\n        items,\n        direction,\n        separation,\n        wrap,\n        transform,\n    }\n    .into()\n}\n"
  },
  {
    "path": "crates/core/src/widget/component/containers/float_box.rs",
    "content": "use crate::{\n    MessageData, PropsData, Scalar, make_widget, pre_hooks,\n    widget::{\n        WidgetId, WidgetIdOrRef,\n        component::{\n            containers::content_box::{ContentBoxProps, content_box},\n            interactive::navigation::{\n                NavContainerActive, NavItemActive, NavJumpActive, use_nav_container_active,\n                use_nav_item, use_nav_jump_direction_active,\n            },\n        },\n        context::{WidgetContext, WidgetMountOrChangeContext},\n        node::WidgetNode,\n        unit::content::ContentBoxContentReposition,\n        utils::Vec2,\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Default, Copy, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct FloatBoxProps {\n    #[serde(default)]\n    pub bounds_left: Option<Scalar>,\n    #[serde(default)]\n    pub bounds_right: Option<Scalar>,\n    #[serde(default)]\n    pub bounds_top: Option<Scalar>,\n    #[serde(default)]\n    pub bounds_bottom: Option<Scalar>,\n}\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct FloatBoxNotifyProps(\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"WidgetIdOrRef::is_none\")]\n    pub WidgetIdOrRef,\n);\n\n#[derive(PropsData, Debug, Copy, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct FloatBoxState {\n    #[serde(default)]\n    pub position: Vec2,\n    #[serde(default = \"FloatBoxState::default_zoom\")]\n    pub zoom: Scalar,\n}\n\nimpl Default for FloatBoxState {\n    fn default() -> Self {\n        Self {\n            position: Default::default(),\n            zoom: Self::default_zoom(),\n        }\n    }\n}\n\nimpl FloatBoxState {\n    fn default_zoom() -> Scalar {\n        1.0\n    }\n}\n\n#[derive(MessageData, Debug, Default, Clone)]\n#[message_data(crate::messenger::MessageData)]\npub struct FloatBoxNotifyMessage {\n    pub sender: WidgetId,\n    pub state: FloatBoxState,\n    pub prev: FloatBoxState,\n}\n\n#[derive(MessageData, Debug, Clone)]\n#[message_data(crate::messenger::MessageData)]\npub struct FloatBoxChangeMessage {\n    pub sender: WidgetId,\n    pub change: FloatBoxChange,\n}\n\n#[derive(Debug, Clone)]\npub enum FloatBoxChange {\n    Absolute(FloatBoxState),\n    RelativePosition(Vec2),\n    RelativeZoom(Scalar),\n}\n\npub fn use_float_box(context: &mut WidgetContext) {\n    fn notify(context: &WidgetMountOrChangeContext, data: FloatBoxNotifyMessage) {\n        if let Ok(FloatBoxNotifyProps(notify)) = context.props.read()\n            && let Some(to) = notify.read()\n        {\n            context.messenger.write(to, data);\n        }\n    }\n\n    context.life_cycle.mount(|context| {\n        let props = context.props.read_cloned_or_default::<FloatBoxProps>();\n        let mut data = context.props.read_cloned_or_default::<FloatBoxState>();\n        if let Some(limit) = props.bounds_left {\n            data.position.x = data.position.x.max(limit);\n        }\n        if let Some(limit) = props.bounds_right {\n            data.position.x = data.position.x.min(limit);\n        }\n        if let Some(limit) = props.bounds_top {\n            data.position.y = data.position.y.max(limit);\n        }\n        if let Some(limit) = props.bounds_bottom {\n            data.position.y = data.position.y.min(limit);\n        }\n        notify(\n            &context,\n            FloatBoxNotifyMessage {\n                sender: context.id.to_owned(),\n                state: data,\n                prev: data,\n            },\n        );\n        let _ = context.state.write_with(data);\n    });\n\n    context.life_cycle.change(|context| {\n        let props = context.props.read_cloned_or_default::<FloatBoxProps>();\n        let mut dirty = false;\n        let mut data = context.state.read_cloned_or_default::<FloatBoxState>();\n        let prev = data;\n        for msg in context.messenger.messages {\n            if let Some(msg) = msg.as_any().downcast_ref::<FloatBoxChangeMessage>() {\n                match msg.change {\n                    FloatBoxChange::Absolute(value) => {\n                        data = value;\n                        dirty = true;\n                    }\n                    FloatBoxChange::RelativePosition(delta) => {\n                        data.position.x -= delta.x;\n                        data.position.y -= delta.y;\n                        dirty = true;\n                    }\n                    FloatBoxChange::RelativeZoom(delta) => {\n                        data.zoom *= delta;\n                        dirty = true;\n                    }\n                }\n            }\n        }\n        if dirty {\n            if let Some(limit) = props.bounds_left {\n                data.position.x = data.position.x.max(limit);\n            }\n            if let Some(limit) = props.bounds_right {\n                data.position.x = data.position.x.min(limit);\n            }\n            if let Some(limit) = props.bounds_top {\n                data.position.y = data.position.y.max(limit);\n            }\n            if let Some(limit) = props.bounds_bottom {\n                data.position.y = data.position.y.min(limit);\n            }\n            notify(\n                &context,\n                FloatBoxNotifyMessage {\n                    sender: context.id.to_owned(),\n                    state: data.to_owned(),\n                    prev,\n                },\n            );\n            let _ = context.state.write_with(data);\n        }\n    });\n}\n\n#[pre_hooks(use_nav_container_active, use_nav_jump_direction_active, use_nav_item)]\npub fn nav_float_box(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        key,\n        props,\n        listed_slots,\n        ..\n    } = context;\n\n    let props = props\n        .clone()\n        .without::<NavContainerActive>()\n        .without::<NavJumpActive>()\n        .without::<NavItemActive>();\n\n    make_widget!(float_box)\n        .key(key)\n        .merge_props(props)\n        .listed_slots(listed_slots)\n        .into()\n}\n\n#[pre_hooks(use_float_box)]\npub fn float_box(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        key,\n        props,\n        state,\n        mut listed_slots,\n        ..\n    } = context;\n\n    let mut props = props.read_cloned_or_default::<ContentBoxProps>();\n    let state = state.read_cloned_or_default::<FloatBoxState>();\n    props.content_reposition = ContentBoxContentReposition {\n        offset: Vec2 {\n            x: -state.position.x,\n            y: -state.position.y,\n        },\n        scale: Vec2 {\n            x: state.zoom,\n            y: state.zoom,\n        },\n    };\n\n    for item in listed_slots.iter_mut() {\n        if let Some(p) = item.props_mut() {\n            p.write(state);\n            if !p.has::<FloatBoxNotifyProps>() {\n                p.write(FloatBoxNotifyProps(context.id.to_owned().into()));\n            }\n        }\n    }\n\n    make_widget!(content_box)\n        .key(key)\n        .with_props(props)\n        .listed_slots(listed_slots)\n        .into()\n}\n"
  },
  {
    "path": "crates/core/src/widget/component/containers/grid_box.rs",
    "content": "use crate::{\n    PropsData, make_widget, pre_hooks,\n    widget::{\n        component::interactive::navigation::{\n            NavContainerActive, NavItemActive, NavJumpActive, use_nav_container_active,\n            use_nav_item, use_nav_jump_direction_active,\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::grid::{GridBoxItemLayout, GridBoxItemNode, GridBoxNode},\n        utils::Transform,\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct GridBoxProps {\n    #[serde(default)]\n    pub cols: usize,\n    #[serde(default)]\n    pub rows: usize,\n    #[serde(default)]\n    pub transform: Transform,\n}\n\n#[pre_hooks(use_nav_container_active, use_nav_jump_direction_active, use_nav_item)]\npub fn nav_grid_box(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        key,\n        props,\n        listed_slots,\n        ..\n    } = context;\n\n    let props = props\n        .clone()\n        .without::<NavContainerActive>()\n        .without::<NavJumpActive>()\n        .without::<NavItemActive>();\n\n    make_widget!(grid_box)\n        .key(key)\n        .merge_props(props)\n        .listed_slots(listed_slots)\n        .into()\n}\n\npub fn grid_box(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        id,\n        props,\n        listed_slots,\n        ..\n    } = context;\n\n    let GridBoxProps {\n        cols,\n        rows,\n        transform,\n    } = props.read_cloned_or_default();\n\n    let items = listed_slots\n        .into_iter()\n        .filter_map(|slot| {\n            if let Some(props) = slot.props() {\n                let layout = props.read_cloned_or_default::<GridBoxItemLayout>();\n                Some(GridBoxItemNode { slot, layout })\n            } else {\n                None\n            }\n        })\n        .collect::<Vec<_>>();\n\n    GridBoxNode {\n        id: id.to_owned(),\n        props: props.clone(),\n        items,\n        cols,\n        rows,\n        transform,\n    }\n    .into()\n}\n"
  },
  {
    "path": "crates/core/src/widget/component/containers/hidden_box.rs",
    "content": "use crate::{\n    PropsData, unpack_named_slots,\n    widget::{context::WidgetContext, node::WidgetNode, unit::area::AreaBoxNode},\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Default, Copy, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct HiddenBoxProps(#[serde(default)] pub bool);\n\npub fn hidden_box(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        id,\n        props,\n        named_slots,\n        ..\n    } = context;\n    unpack_named_slots!(named_slots => content);\n\n    let HiddenBoxProps(hidden) = props.read_cloned_or_default();\n\n    if hidden {\n        Default::default()\n    } else {\n        AreaBoxNode {\n            id: id.to_owned(),\n            slot: Box::new(content),\n        }\n        .into()\n    }\n}\n"
  },
  {
    "path": "crates/core/src/widget/component/containers/horizontal_box.rs",
    "content": "use crate::{\n    PropsData, Scalar, make_widget, pre_hooks,\n    widget::{\n        component::{\n            containers::flex_box::{FlexBoxProps, flex_box},\n            interactive::navigation::{\n                NavContainerActive, NavItemActive, NavJumpActive, use_nav_container_active,\n                use_nav_item, use_nav_jump_horizontal_step_active,\n            },\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::flex::{FlexBoxDirection, FlexBoxItemLayout},\n        utils::Transform,\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct HorizontalBoxProps {\n    #[serde(default)]\n    pub separation: Scalar,\n    #[serde(default)]\n    pub reversed: bool,\n    #[serde(default)]\n    pub override_slots_layout: Option<FlexBoxItemLayout>,\n    #[serde(default)]\n    pub transform: Transform,\n}\n\n#[pre_hooks(\n    use_nav_container_active,\n    use_nav_jump_horizontal_step_active,\n    use_nav_item\n)]\npub fn nav_horizontal_box(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        key,\n        props,\n        listed_slots,\n        ..\n    } = context;\n\n    let props = props\n        .clone()\n        .without::<NavContainerActive>()\n        .without::<NavJumpActive>()\n        .without::<NavItemActive>();\n\n    make_widget!(horizontal_box)\n        .key(key)\n        .merge_props(props)\n        .listed_slots(listed_slots)\n        .into()\n}\n\npub fn horizontal_box(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        key,\n        props,\n        mut listed_slots,\n        ..\n    } = context;\n\n    let HorizontalBoxProps {\n        separation,\n        reversed,\n        override_slots_layout,\n        transform,\n    } = props.read_cloned_or_default();\n\n    if let Some(layout) = override_slots_layout {\n        for slot in &mut listed_slots {\n            if let Some(props) = slot.props_mut() {\n                props.write(layout.to_owned());\n            }\n        }\n    }\n\n    let props = props.clone().with(FlexBoxProps {\n        direction: if reversed {\n            FlexBoxDirection::HorizontalRightToLeft\n        } else {\n            FlexBoxDirection::HorizontalLeftToRight\n        },\n        separation,\n        wrap: false,\n        transform,\n    });\n\n    make_widget!(flex_box)\n        .key(key)\n        .merge_props(props)\n        .listed_slots(listed_slots)\n        .into()\n}\n"
  },
  {
    "path": "crates/core/src/widget/component/containers/mod.rs",
    "content": "//! Containers for other components\n\npub mod anchor_box;\npub mod area_box;\npub mod content_box;\npub mod context_box;\npub mod flex_box;\npub mod float_box;\npub mod grid_box;\npub mod hidden_box;\npub mod horizontal_box;\npub mod portal_box;\npub mod responsive_box;\npub mod scroll_box;\npub mod size_box;\npub mod switch_box;\npub mod tabs_box;\npub mod tooltip_box;\npub mod variant_box;\npub mod vertical_box;\npub mod wrap_box;\n"
  },
  {
    "path": "crates/core/src/widget/component/containers/portal_box.rs",
    "content": "use crate::{\n    PropsData, unpack_named_slots,\n    widget::{\n        WidgetRef,\n        component::RelativeLayoutProps,\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::{\n            content::{ContentBoxItemLayout, ContentBoxItemNode},\n            flex::{FlexBoxItemLayout, FlexBoxItemNode},\n            grid::{GridBoxItemLayout, GridBoxItemNode},\n            portal::{PortalBoxNode, PortalBoxSlotNode},\n        },\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct PortalsContainer(#[serde(default)] pub WidgetRef);\n\npub fn portal_box(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        id,\n        props,\n        shared_props,\n        named_slots,\n        ..\n    } = context;\n    unpack_named_slots!(named_slots => content);\n\n    let PortalsContainer(owner) =\n        props.read_cloned_or_else(|| shared_props.read_cloned_or_default());\n    let slot = if let Ok(layout) = props.read_cloned::<ContentBoxItemLayout>() {\n        PortalBoxSlotNode::ContentItem(ContentBoxItemNode {\n            slot: content,\n            layout,\n        })\n    } else if let Ok(layout) = props.read_cloned::<FlexBoxItemLayout>() {\n        PortalBoxSlotNode::FlexItem(FlexBoxItemNode {\n            slot: content,\n            layout,\n        })\n    } else if let Ok(layout) = props.read_cloned::<GridBoxItemLayout>() {\n        PortalBoxSlotNode::GridItem(GridBoxItemNode {\n            slot: content,\n            layout,\n        })\n    } else {\n        PortalBoxSlotNode::Slot(content)\n    };\n\n    if let Some(owner) = owner.read() {\n        PortalBoxNode {\n            id: id.to_owned(),\n            slot: Box::new(slot),\n            owner,\n        }\n        .into()\n    } else {\n        Default::default()\n    }\n}\n\npub fn use_portals_container_relative_layout(context: &mut WidgetContext) {\n    let PortalsContainer(owner) = context\n        .props\n        .read_cloned_or_else(|| context.shared_props.read_cloned_or_default());\n    context.props.write(RelativeLayoutProps {\n        relative_to: owner.into(),\n    });\n}\n"
  },
  {
    "path": "crates/core/src/widget/component/containers/responsive_box.rs",
    "content": "use crate::{\n    PropsData, Scalar, pre_hooks, unpack_named_slots,\n    view_model::{ViewModelProperties, ViewModelValue},\n    widget::{\n        component::{ResizeListenerSignal, use_resize_listener},\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::area::AreaBoxNode,\n        utils::Vec2,\n    },\n};\nuse serde::{Deserialize, Serialize};\nuse std::collections::{HashMap, HashSet};\n\npub struct MediaQueryContext<'a> {\n    pub widget_width: Scalar,\n    pub widget_height: Scalar,\n    pub view_model: Option<&'a MediaQueryViewModel>,\n}\n\n#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]\npub enum MediaQueryOrientation {\n    #[default]\n    Portrait,\n    Landscape,\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]\npub enum MediaQueryNumber {\n    Exact(Scalar),\n    Min(Scalar),\n    Max(Scalar),\n    Range { min: Scalar, max: Scalar },\n}\n\nimpl Default for MediaQueryNumber {\n    fn default() -> Self {\n        Self::Exact(0.0)\n    }\n}\n\nimpl MediaQueryNumber {\n    pub fn is_valid(&self, value: Scalar) -> bool {\n        match self {\n            Self::Exact(v) => (*v - value).abs() < Scalar::EPSILON,\n            Self::Min(v) => *v <= value,\n            Self::Max(v) => *v >= value,\n            Self::Range { min, max } => *min <= value && *max >= value,\n        }\n    }\n}\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub enum MediaQueryExpression {\n    #[default]\n    Any,\n    And(Vec<Self>),\n    Or(Vec<Self>),\n    Not(Box<Self>),\n    WidgetOrientation(MediaQueryOrientation),\n    WidgetAspectRatio(MediaQueryNumber),\n    WidgetWidth(MediaQueryNumber),\n    WidgetHeight(MediaQueryNumber),\n    ScreenOrientation(MediaQueryOrientation),\n    ScreenAspectRatio(MediaQueryNumber),\n    ScreenWidth(MediaQueryNumber),\n    ScreenHeight(MediaQueryNumber),\n    HasFlag(String),\n    HasNumber(String),\n    Number(String, MediaQueryNumber),\n}\n\nimpl MediaQueryExpression {\n    pub fn is_valid(&self, context: &MediaQueryContext) -> bool {\n        match self {\n            Self::Any => true,\n            Self::And(conditions) => conditions\n                .iter()\n                .all(|condition| condition.is_valid(context)),\n            Self::Or(conditions) => conditions\n                .iter()\n                .any(|condition| condition.is_valid(context)),\n            Self::Not(condition) => !condition.is_valid(context),\n            Self::WidgetOrientation(orientation) => {\n                let is_portrait = context.widget_height > context.widget_width;\n                match orientation {\n                    MediaQueryOrientation::Portrait => is_portrait,\n                    MediaQueryOrientation::Landscape => !is_portrait,\n                }\n            }\n            Self::WidgetAspectRatio(aspect_ratio) => {\n                let ratio = context.widget_width / context.widget_height;\n                aspect_ratio.is_valid(ratio)\n            }\n            Self::WidgetWidth(width) => width.is_valid(context.widget_width),\n            Self::WidgetHeight(height) => height.is_valid(context.widget_height),\n            Self::ScreenOrientation(orientation) => context\n                .view_model\n                .map(|view_model| {\n                    let is_portrait = view_model.screen_size.y > view_model.screen_size.x;\n                    match orientation {\n                        MediaQueryOrientation::Portrait => is_portrait,\n                        MediaQueryOrientation::Landscape => !is_portrait,\n                    }\n                })\n                .unwrap_or_default(),\n            Self::ScreenAspectRatio(aspect_ratio) => context\n                .view_model\n                .map(|view_model| {\n                    let ratio = view_model.screen_size.x / view_model.screen_size.y;\n                    aspect_ratio.is_valid(ratio)\n                })\n                .unwrap_or_default(),\n            Self::ScreenWidth(width) => context\n                .view_model\n                .map(|view_model| width.is_valid(view_model.screen_size.x))\n                .unwrap_or_default(),\n            Self::ScreenHeight(height) => context\n                .view_model\n                .map(|view_model| height.is_valid(view_model.screen_size.y))\n                .unwrap_or_default(),\n            Self::HasFlag(flag) => context\n                .view_model\n                .map(|view_model| view_model.flags.contains(flag))\n                .unwrap_or_default(),\n            Self::HasNumber(name) => context\n                .view_model\n                .map(|view_model| view_model.numbers.contains_key(name))\n                .unwrap_or_default(),\n            Self::Number(name, number) => context\n                .view_model\n                .map(|view_model| {\n                    view_model\n                        .numbers\n                        .get(name)\n                        .map(|value| number.is_valid(*value))\n                        .unwrap_or_default()\n                })\n                .unwrap_or_default(),\n        }\n    }\n}\n\n#[derive(PropsData, Debug, Default, Copy, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct ResponsiveBoxState {\n    pub size: Vec2,\n}\n\npub fn use_responsive_box(context: &mut WidgetContext) {\n    context.life_cycle.mount(|mut context| {\n        if let Some(mut bindings) = context.view_models.bindings(\n            MediaQueryViewModel::VIEW_MODEL,\n            MediaQueryViewModel::NOTIFIER,\n        ) {\n            bindings.bind(context.id.to_owned());\n        }\n    });\n\n    context.life_cycle.unmount(|mut context| {\n        if let Some(mut bindings) = context.view_models.bindings(\n            MediaQueryViewModel::VIEW_MODEL,\n            MediaQueryViewModel::NOTIFIER,\n        ) {\n            bindings.unbind(context.id);\n        }\n    });\n\n    context.life_cycle.change(|context| {\n        for msg in context.messenger.messages {\n            if let Some(ResizeListenerSignal::Change(size)) = msg.as_any().downcast_ref() {\n                let _ = context.state.write(ResponsiveBoxState { size: *size });\n            }\n        }\n    });\n}\n\n#[pre_hooks(use_resize_listener, use_responsive_box)]\npub fn responsive_box(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        id,\n        state,\n        mut listed_slots,\n        view_models,\n        ..\n    } = context;\n\n    let state = state.read_cloned_or_default::<ResponsiveBoxState>();\n    let view_model = view_models\n        .view_model(MediaQueryViewModel::VIEW_MODEL)\n        .and_then(|vm| vm.read::<MediaQueryViewModel>());\n    let ctx = MediaQueryContext {\n        widget_width: state.size.x,\n        widget_height: state.size.y,\n        view_model: view_model.as_deref(),\n    };\n    let item = if let Some(index) = listed_slots.iter().position(|slot| {\n        slot.props()\n            .map(|props| {\n                props\n                    .read::<MediaQueryExpression>()\n                    .ok()\n                    .map(|query| query.is_valid(&ctx))\n                    .unwrap_or(true)\n            })\n            .unwrap_or_default()\n    }) {\n        listed_slots.remove(index)\n    } else {\n        Default::default()\n    };\n\n    AreaBoxNode {\n        id: id.to_owned(),\n        slot: Box::new(item),\n    }\n    .into()\n}\n\n#[pre_hooks(use_resize_listener, use_responsive_box)]\npub fn responsive_props_box(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        id,\n        state,\n        listed_slots,\n        named_slots,\n        view_models,\n        ..\n    } = context;\n    unpack_named_slots!(named_slots => content);\n\n    let state = state.read_cloned_or_default::<ResponsiveBoxState>();\n    let view_model = view_models\n        .view_model(MediaQueryViewModel::VIEW_MODEL)\n        .and_then(|vm| vm.read::<MediaQueryViewModel>());\n    let ctx = MediaQueryContext {\n        widget_width: state.size.x,\n        widget_height: state.size.y,\n        view_model: view_model.as_deref(),\n    };\n\n    let props = listed_slots\n        .iter()\n        .find_map(|slot| {\n            slot.props().and_then(|props| {\n                props\n                    .read::<MediaQueryExpression>()\n                    .ok()\n                    .map(|query| query.is_valid(&ctx))\n                    .unwrap_or(true)\n                    .then_some(props.clone())\n            })\n        })\n        .unwrap_or_default();\n\n    if let Some(p) = content.props_mut() {\n        p.merge_from(props.without::<MediaQueryExpression>());\n    }\n\n    AreaBoxNode {\n        id: id.to_owned(),\n        slot: Box::new(content),\n    }\n    .into()\n}\n\n#[derive(Debug)]\npub struct MediaQueryViewModel {\n    pub screen_size: ViewModelValue<Vec2>,\n    pub flags: ViewModelValue<HashSet<String>>,\n    pub numbers: ViewModelValue<HashMap<String, Scalar>>,\n}\n\nimpl MediaQueryViewModel {\n    pub const VIEW_MODEL: &str = \"MediaQueryViewModel\";\n    pub const NOTIFIER: &str = \"\";\n\n    pub fn new(properties: &mut ViewModelProperties) -> Self {\n        let notifier = properties.notifier(Self::NOTIFIER);\n        Self {\n            screen_size: ViewModelValue::new(Default::default(), notifier.clone()),\n            flags: ViewModelValue::new(Default::default(), notifier.clone()),\n            numbers: ViewModelValue::new(Default::default(), notifier.clone()),\n        }\n    }\n}\n"
  },
  {
    "path": "crates/core/src/widget/component/containers/scroll_box.rs",
    "content": "use crate::{\n    PropsData, Scalar, make_widget, pre_hooks,\n    props::Props,\n    unpack_named_slots,\n    widget::{\n        WidgetId,\n        component::{\n            ResizeListenerSignal,\n            containers::{\n                content_box::{ContentBoxProps, content_box},\n                size_box::{SizeBoxProps, size_box},\n            },\n            image_box::{ImageBoxProps, image_box},\n            interactive::{\n                button::{\n                    ButtonNotifyMessage, ButtonNotifyProps, ButtonProps, button,\n                    self_tracked_button,\n                },\n                navigation::{\n                    NavItemActive, NavJump, NavScroll, NavSignal, NavTrackingNotifyMessage,\n                    NavTrackingNotifyProps, use_nav_container_active, use_nav_item,\n                    use_nav_item_active, use_nav_scroll_view_content,\n                },\n                scroll_view::{ScrollViewState, use_scroll_view},\n            },\n            use_resize_listener,\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::{\n            area::AreaBoxNode, content::ContentBoxItemLayout, image::ImageBoxMaterial,\n            size::SizeBoxSizeValue,\n        },\n        utils::{Rect, Vec2, lerp},\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct ScrollBoxOwner(\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"WidgetId::is_none\")]\n    pub WidgetId,\n);\n\n#[derive(PropsData, Debug, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct SideScrollbarsProps {\n    #[serde(default)]\n    pub size: Scalar,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub back_material: Option<ImageBoxMaterial>,\n    #[serde(default)]\n    pub front_material: ImageBoxMaterial,\n}\n\nimpl Default for SideScrollbarsProps {\n    fn default() -> Self {\n        Self {\n            size: 10.0,\n            back_material: None,\n            front_material: Default::default(),\n        }\n    }\n}\n\n#[derive(PropsData, Debug, Default, Copy, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct SideScrollbarsState {\n    pub horizontal_state: ButtonProps,\n    pub vertical_state: ButtonProps,\n}\n\npub fn use_nav_scroll_box_content(context: &mut WidgetContext) {\n    context.life_cycle.change(|context| {\n        for msg in context.messenger.messages {\n            if let Some(ResizeListenerSignal::Change(size)) = msg.as_any().downcast_ref()\n                && let Ok(data) = context.props.read::<ScrollBoxOwner>()\n            {\n                context\n                    .messenger\n                    .write(data.0.to_owned(), ResizeListenerSignal::Change(*size));\n            }\n        }\n    });\n}\n\n#[pre_hooks(\n    use_resize_listener,\n    use_nav_item_active,\n    use_nav_container_active,\n    use_nav_scroll_view_content,\n    use_nav_scroll_box_content\n)]\npub fn nav_scroll_box_content(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        id, named_slots, ..\n    } = context;\n    unpack_named_slots!(named_slots => content);\n\n    AreaBoxNode {\n        id: id.to_owned(),\n        slot: Box::new(content),\n    }\n    .into()\n}\n\npub fn use_nav_scroll_box(context: &mut WidgetContext) {\n    context.life_cycle.change(|context| {\n        for msg in context.messenger.messages {\n            if let Some(ResizeListenerSignal::Change(_)) = msg.as_any().downcast_ref()\n                && let Ok(data) = context.state.read::<ScrollViewState>()\n            {\n                context\n                    .signals\n                    .write(NavSignal::Jump(NavJump::Scroll(NavScroll::Factor(\n                        data.value, false,\n                    ))));\n            }\n        }\n    });\n}\n\n#[pre_hooks(\n    use_resize_listener,\n    use_nav_item,\n    use_nav_container_active,\n    use_scroll_view,\n    use_nav_scroll_box\n)]\npub fn nav_scroll_box(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        id,\n        key,\n        props,\n        state,\n        named_slots,\n        ..\n    } = context;\n    unpack_named_slots!(named_slots => {content, scrollbars});\n\n    let scroll_props = state.read_cloned_or_default::<ScrollViewState>();\n\n    let content_props = Props::new(ContentBoxItemLayout {\n        align: scroll_props.value,\n        ..Default::default()\n    })\n    .with(ScrollBoxOwner(id.to_owned()));\n\n    if let Some(props) = scrollbars.props_mut() {\n        props.write(ScrollBoxOwner(id.to_owned()));\n        props.write(scroll_props);\n    }\n\n    if !props.has::<ContentBoxProps>() {\n        props.write(ContentBoxProps {\n            clipping: true,\n            ..Default::default()\n        });\n    }\n\n    let size_props = SizeBoxProps {\n        width: SizeBoxSizeValue::Fill,\n        height: SizeBoxSizeValue::Fill,\n        ..Default::default()\n    };\n\n    let content = make_widget!(content_box)\n        .key(key)\n        .merge_props(props.clone())\n        .listed_slot(\n            make_widget!(button)\n                .key(\"input-consumer\")\n                .with_props(NavItemActive)\n                .named_slot(\n                    \"content\",\n                    make_widget!(size_box).key(\"size\").with_props(size_props),\n                ),\n        )\n        .listed_slot(\n            make_widget!(nav_scroll_box_content)\n                .key(\"content\")\n                .merge_props(content_props)\n                .named_slot(\"content\", content),\n        )\n        .listed_slot(scrollbars)\n        .into();\n\n    AreaBoxNode {\n        id: id.to_owned(),\n        slot: Box::new(content),\n    }\n    .into()\n}\n\npub fn use_nav_scroll_box_side_scrollbars(context: &mut WidgetContext) {\n    context.life_cycle.mount(|context| {\n        let _ = context.state.write_with(SideScrollbarsState::default());\n    });\n\n    context.life_cycle.unmount(|context| {\n        context.signals.write(NavSignal::Unlock);\n    });\n\n    context.life_cycle.change(|context| {\n        let mut dirty = false;\n        let mut notify = false;\n        let mut state = context\n            .state\n            .read_cloned_or_default::<SideScrollbarsState>();\n        let mut props = context.props.read_cloned_or_default::<ScrollViewState>();\n        for msg in context.messenger.messages {\n            if let Some(msg) = msg.as_any().downcast_ref::<ButtonNotifyMessage>() {\n                if msg.trigger_start() {\n                    context.signals.write(NavSignal::Lock);\n                }\n                if msg.trigger_stop() {\n                    context.signals.write(NavSignal::Unlock);\n                }\n                if msg.sender.key() == \"hbar\" {\n                    state.horizontal_state = msg.state;\n                    dirty = true;\n                } else if msg.sender.key() == \"vbar\" {\n                    state.vertical_state = msg.state;\n                    dirty = true;\n                }\n            }\n            if let Some(msg) = msg.as_any().downcast_ref::<NavTrackingNotifyMessage>() {\n                if msg.sender.key() == \"hbar\"\n                    && state.horizontal_state.selected\n                    && (state.horizontal_state.trigger || state.horizontal_state.context)\n                {\n                    props.value.x = msg.state.factor.x;\n                    notify = true;\n                } else if msg.sender.key() == \"vbar\"\n                    && state.vertical_state.selected\n                    && (state.vertical_state.trigger || state.vertical_state.context)\n                {\n                    props.value.y = msg.state.factor.y;\n                    notify = true;\n                }\n            }\n        }\n        if dirty {\n            let _ = context.state.write_with(state);\n        }\n        if notify {\n            let view = context.props.read_cloned_or_default::<ScrollBoxOwner>().0;\n            context\n                .signals\n                .write(NavSignal::Jump(NavJump::Scroll(NavScroll::DirectFactor(\n                    view.into(),\n                    props.value,\n                    false,\n                ))));\n        }\n    });\n}\n\n#[pre_hooks(\n    use_nav_item_active,\n    use_nav_container_active,\n    use_nav_scroll_box_side_scrollbars\n)]\npub fn nav_scroll_box_side_scrollbars(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext { id, key, props, .. } = context;\n\n    let view_props = props.read_cloned_or_default::<ScrollViewState>();\n\n    let SideScrollbarsProps {\n        size,\n        back_material,\n        front_material,\n    } = props.read_cloned_or_default();\n\n    let hbar = if view_props.size_factor.x > 1.0 {\n        let length = 1.0 / view_props.size_factor.y;\n        let rest = 1.0 - length;\n\n        let button_props = Props::new(NavItemActive)\n            .with(ButtonNotifyProps(id.to_owned().into()))\n            .with(NavTrackingNotifyProps(id.to_owned().into()))\n            .with(ContentBoxItemLayout {\n                anchors: Rect {\n                    left: 0.0,\n                    right: 1.0,\n                    top: 1.0,\n                    bottom: 1.0,\n                },\n                margin: Rect {\n                    left: 0.0,\n                    right: size,\n                    top: -size,\n                    bottom: 0.0,\n                },\n                align: Vec2 { x: 0.0, y: 1.0 },\n                ..Default::default()\n            });\n\n        let front_props = Props::new(ImageBoxProps {\n            material: front_material.clone(),\n            ..Default::default()\n        })\n        .with(ContentBoxItemLayout {\n            anchors: Rect {\n                left: lerp(0.0, rest, view_props.value.x),\n                right: lerp(length, 1.0, view_props.value.x),\n                top: 0.0,\n                bottom: 1.0,\n            },\n            ..Default::default()\n        });\n\n        let back = if let Some(material) = back_material.clone() {\n            let props = ImageBoxProps {\n                material,\n                ..Default::default()\n            };\n\n            make_widget!(image_box).key(\"back\").with_props(props).into()\n        } else {\n            WidgetNode::default()\n        };\n\n        make_widget!(self_tracked_button)\n            .key(\"hbar\")\n            .merge_props(button_props)\n            .named_slot(\n                \"content\",\n                make_widget!(content_box)\n                    .key(\"container\")\n                    .listed_slot(back)\n                    .listed_slot(\n                        make_widget!(image_box)\n                            .key(\"front\")\n                            .merge_props(front_props),\n                    ),\n            )\n            .into()\n    } else {\n        WidgetNode::default()\n    };\n\n    let vbar = if view_props.size_factor.y > 1.0 {\n        let length = 1.0 / view_props.size_factor.y;\n        let rest = 1.0 - length;\n\n        let button_props = Props::new(NavItemActive)\n            .with(ButtonNotifyProps(id.to_owned().into()))\n            .with(NavTrackingNotifyProps(id.to_owned().into()))\n            .with(ContentBoxItemLayout {\n                anchors: Rect {\n                    left: 1.0,\n                    right: 1.0,\n                    top: 0.0,\n                    bottom: 1.0,\n                },\n                margin: Rect {\n                    left: -size,\n                    right: 0.0,\n                    top: 0.0,\n                    bottom: size,\n                },\n                align: Vec2 { x: 1.0, y: 0.0 },\n                ..Default::default()\n            });\n\n        let back = if let Some(material) = back_material {\n            let props = ImageBoxProps {\n                material,\n                ..Default::default()\n            };\n\n            make_widget!(image_box).key(\"back\").with_props(props).into()\n        } else {\n            WidgetNode::default()\n        };\n\n        let front_props = Props::new(ImageBoxProps {\n            material: front_material,\n            ..Default::default()\n        })\n        .with(ContentBoxItemLayout {\n            anchors: Rect {\n                left: 0.0,\n                right: 1.0,\n                top: lerp(0.0, rest, view_props.value.y),\n                bottom: lerp(length, 1.0, view_props.value.y),\n            },\n            ..Default::default()\n        });\n\n        make_widget!(self_tracked_button)\n            .key(\"vbar\")\n            .merge_props(button_props)\n            .named_slot(\n                \"content\",\n                make_widget!(content_box)\n                    .key(\"container\")\n                    .listed_slot(back)\n                    .listed_slot(\n                        make_widget!(image_box)\n                            .key(\"front\")\n                            .merge_props(front_props),\n                    ),\n            )\n            .into()\n    } else {\n        WidgetNode::default()\n    };\n\n    make_widget!(content_box)\n        .key(key)\n        .listed_slot(hbar)\n        .listed_slot(vbar)\n        .into()\n}\n"
  },
  {
    "path": "crates/core/src/widget/component/containers/size_box.rs",
    "content": "use crate::{\n    PropsData, unpack_named_slots,\n    widget::{\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::size::{SizeBoxAspectRatio, SizeBoxNode, SizeBoxSizeValue},\n        utils::{Rect, Transform},\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct SizeBoxProps {\n    #[serde(default)]\n    pub width: SizeBoxSizeValue,\n    #[serde(default)]\n    pub height: SizeBoxSizeValue,\n    #[serde(default)]\n    pub margin: Rect,\n    #[serde(default)]\n    pub keep_aspect_ratio: SizeBoxAspectRatio,\n    #[serde(default)]\n    pub transform: Transform,\n}\n\npub fn size_box(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        id,\n        props,\n        named_slots,\n        ..\n    } = context;\n    unpack_named_slots!(named_slots => content);\n\n    let SizeBoxProps {\n        width,\n        height,\n        margin,\n        keep_aspect_ratio,\n        transform,\n    } = props.read_cloned_or_default();\n\n    SizeBoxNode {\n        id: id.to_owned(),\n        props: props.clone(),\n        slot: Box::new(content),\n        width,\n        height,\n        margin,\n        keep_aspect_ratio,\n        transform,\n    }\n    .into()\n}\n"
  },
  {
    "path": "crates/core/src/widget/component/containers/switch_box.rs",
    "content": "use crate::{\n    PropsData, make_widget, pre_hooks,\n    widget::{\n        component::interactive::navigation::{\n            NavContainerActive, NavItemActive, NavJumpActive, use_nav_container_active,\n            use_nav_item, use_nav_jump_step_pages_active,\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::content::{ContentBoxItemNode, ContentBoxNode},\n        utils::Transform,\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct SwitchBoxProps {\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub active_index: Option<usize>,\n    #[serde(default)]\n    pub clipping: bool,\n    #[serde(default)]\n    pub transform: Transform,\n}\n\n#[pre_hooks(use_nav_container_active, use_nav_jump_step_pages_active, use_nav_item)]\npub fn nav_switch_box(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        key,\n        props,\n        listed_slots,\n        ..\n    } = context;\n\n    let props = props\n        .clone()\n        .without::<NavContainerActive>()\n        .without::<NavJumpActive>()\n        .without::<NavItemActive>();\n\n    make_widget!(switch_box)\n        .key(key)\n        .merge_props(props)\n        .listed_slots(listed_slots)\n        .into()\n}\n\npub fn switch_box(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        id,\n        props,\n        listed_slots,\n        ..\n    } = context;\n\n    let SwitchBoxProps {\n        active_index,\n        clipping,\n        transform,\n    } = props.read_cloned_or_default();\n\n    let items = if let Some(index) = active_index {\n        if let Some(slot) = listed_slots.into_iter().nth(index) {\n            vec![ContentBoxItemNode {\n                slot,\n                ..Default::default()\n            }]\n        } else {\n            vec![]\n        }\n    } else {\n        vec![]\n    };\n\n    ContentBoxNode {\n        id: id.to_owned(),\n        props: props.clone(),\n        items,\n        clipping,\n        content_reposition: Default::default(),\n        transform,\n    }\n    .into()\n}\n"
  },
  {
    "path": "crates/core/src/widget/component/containers/tabs_box.rs",
    "content": "use crate::{\n    PropsData, Scalar, make_widget, pre_hooks,\n    props::Props,\n    widget::{\n        component::{\n            containers::{\n                flex_box::{FlexBoxProps, flex_box},\n                switch_box::{SwitchBoxProps, switch_box},\n            },\n            interactive::{\n                button::{ButtonNotifyMessage, ButtonNotifyProps, button},\n                navigation::{NavItemActive, use_nav_container_active, use_nav_item},\n            },\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::flex::{FlexBoxDirection, FlexBoxItemLayout},\n        utils::Transform,\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]\npub enum TabsBoxTabsLocation {\n    #[default]\n    Top,\n    Bottom,\n    Left,\n    Right,\n}\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct TabsBoxProps {\n    #[serde(default)]\n    pub tabs_location: TabsBoxTabsLocation,\n    #[serde(default)]\n    pub tabs_and_content_separation: Scalar,\n    #[serde(default)]\n    pub tabs_basis: Option<Scalar>,\n    #[serde(default)]\n    pub contents_clipping: bool,\n    #[serde(default)]\n    pub start_index: usize,\n    #[serde(default)]\n    pub transform: Transform,\n}\n\n#[derive(PropsData, Debug, Default, Copy, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct TabsState {\n    #[serde(default)]\n    pub active_index: usize,\n}\n\n#[derive(PropsData, Debug, Default, Copy, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct TabPlateProps {\n    #[serde(default)]\n    pub active: bool,\n    #[serde(default)]\n    pub index: usize,\n}\n\nimpl TabsBoxProps {\n    fn to_main_props(&self) -> FlexBoxProps {\n        FlexBoxProps {\n            direction: match self.tabs_location {\n                TabsBoxTabsLocation::Top => FlexBoxDirection::VerticalTopToBottom,\n                TabsBoxTabsLocation::Bottom => FlexBoxDirection::VerticalBottomToTop,\n                TabsBoxTabsLocation::Left => FlexBoxDirection::HorizontalLeftToRight,\n                TabsBoxTabsLocation::Right => FlexBoxDirection::HorizontalRightToLeft,\n            },\n            separation: self.tabs_and_content_separation,\n            wrap: false,\n            transform: self.transform.to_owned(),\n        }\n    }\n\n    fn to_tabs_props(&self) -> FlexBoxProps {\n        FlexBoxProps {\n            direction: match self.tabs_location {\n                TabsBoxTabsLocation::Top => FlexBoxDirection::HorizontalLeftToRight,\n                TabsBoxTabsLocation::Bottom => FlexBoxDirection::HorizontalLeftToRight,\n                TabsBoxTabsLocation::Left => FlexBoxDirection::VerticalTopToBottom,\n                TabsBoxTabsLocation::Right => FlexBoxDirection::VerticalTopToBottom,\n            },\n            ..Default::default()\n        }\n    }\n}\n\npub fn use_nav_tabs_box(context: &mut WidgetContext) {\n    context.life_cycle.mount(|context| {\n        let _ = context.state.write(TabsState {\n            active_index: context\n                .props\n                .map_or_default::<TabsBoxProps, _, _>(|p| p.start_index),\n        });\n    });\n\n    context.life_cycle.change(|context| {\n        for msg in context.messenger.messages {\n            if let Some(msg) = msg.as_any().downcast_ref::<ButtonNotifyMessage>()\n                && msg.trigger_start()\n                && let Ok(index) = msg.sender.key().parse::<usize>()\n            {\n                let _ = context.state.write(TabsState {\n                    active_index: index,\n                });\n            }\n        }\n    })\n}\n\n#[pre_hooks(use_nav_container_active, use_nav_item, use_nav_tabs_box)]\npub fn nav_tabs_box(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        id,\n        key,\n        props,\n        state,\n        listed_slots,\n        ..\n    } = context;\n\n    let main_props = props.read_cloned_or_default::<TabsBoxProps>();\n    let props = props.clone().with(main_props.to_main_props());\n    let tabs_props = Props::new(main_props.to_tabs_props()).with(FlexBoxItemLayout {\n        basis: main_props.tabs_basis,\n        grow: 0.0,\n        shrink: 0.0,\n        ..Default::default()\n    });\n    let TabsState { active_index } = state.read_cloned_or_default();\n    let switch_props = SwitchBoxProps {\n        active_index: if active_index < listed_slots.len() {\n            Some(active_index)\n        } else {\n            None\n        },\n        clipping: main_props.contents_clipping,\n        ..Default::default()\n    };\n    let mut tabs = Vec::<WidgetNode>::with_capacity(listed_slots.len());\n    let mut contents = Vec::with_capacity(listed_slots.len());\n\n    for (index, item) in listed_slots.into_iter().enumerate() {\n        let [mut tab, content] = item.unpack_tuple();\n        tab.remap_props(|props| {\n            props.with(TabPlateProps {\n                active: active_index == index,\n                index,\n            })\n        });\n        let props = Props::new(NavItemActive).with(ButtonNotifyProps(id.to_owned().into()));\n        tabs.push(\n            make_widget!(button)\n                .key(index)\n                .merge_props(props)\n                .named_slot(\"content\", tab)\n                .into(),\n        );\n        contents.push(content);\n    }\n\n    make_widget!(flex_box)\n        .key(key)\n        .merge_props(props)\n        .listed_slot(\n            make_widget!(flex_box)\n                .key(\"tabs\")\n                .merge_props(tabs_props)\n                .listed_slots(tabs),\n        )\n        .listed_slot(\n            make_widget!(switch_box)\n                .key(\"contents\")\n                .with_props(switch_props)\n                .listed_slots(contents),\n        )\n        .into()\n}\n"
  },
  {
    "path": "crates/core/src/widget/component/containers/tooltip_box.rs",
    "content": "use crate::{\n    PropsData, make_widget, pre_hooks,\n    props::Props,\n    unpack_named_slots,\n    widget::{\n        component::{\n            containers::{\n                anchor_box::{AnchorProps, PivotBoxProps, pivot_box, use_anchor_box},\n                content_box::content_box,\n                portal_box::{portal_box, use_portals_container_relative_layout},\n            },\n            interactive::navigation::{NavSignal, use_nav_container_active, use_nav_item_active},\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::area::AreaBoxNode,\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Default, Copy, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct TooltipState {\n    #[serde(default)]\n    pub show: bool,\n}\n\n#[pre_hooks(use_nav_container_active, use_nav_item_active, use_anchor_box)]\npub fn use_tooltip_box(context: &mut WidgetContext) {\n    context.life_cycle.change(|context| {\n        for msg in context.messenger.messages {\n            if let Some(msg) = msg.as_any().downcast_ref() {\n                match msg {\n                    NavSignal::Select(_) => {\n                        let _ = context.state.write_with(TooltipState { show: true });\n                    }\n                    NavSignal::Unselect => {\n                        let _ = context.state.write_with(TooltipState { show: false });\n                    }\n                    _ => {}\n                }\n            }\n        }\n    });\n}\n\n#[pre_hooks(use_tooltip_box)]\npub fn tooltip_box(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        id,\n        idref,\n        key,\n        props,\n        state,\n        named_slots,\n        ..\n    } = context;\n    unpack_named_slots!(named_slots => {content, tooltip});\n\n    let TooltipState { show } = state.read_cloned_or_default();\n    let anchor_state = state.read_cloned_or_default::<AnchorProps>();\n    let pivot_props =\n        Props::new(anchor_state).with(props.read_cloned_or_default::<PivotBoxProps>());\n\n    let tooltip = if show {\n        make_widget!(pivot_box)\n            .key(\"pivot\")\n            .merge_props(pivot_props)\n            .named_slot(\n                \"content\",\n                make_widget!(portal_box)\n                    .key(\"portal\")\n                    .named_slot(\"content\", tooltip),\n            )\n            .into()\n    } else {\n        WidgetNode::default()\n    };\n\n    let content = make_widget!(content_box)\n        .key(key)\n        .maybe_idref(idref.cloned())\n        .merge_props(props.clone())\n        .listed_slot(content)\n        .listed_slot(tooltip)\n        .into();\n\n    AreaBoxNode {\n        id: id.to_owned(),\n        slot: Box::new(content),\n    }\n    .into()\n}\n\n#[pre_hooks(use_portals_container_relative_layout)]\npub fn portals_tooltip_box(mut context: WidgetContext) -> WidgetNode {\n    tooltip_box(context)\n}\n"
  },
  {
    "path": "crates/core/src/widget/component/containers/variant_box.rs",
    "content": "use crate::{\n    PropsData,\n    widget::{context::WidgetContext, node::WidgetNode},\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct VariantBoxProps {\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub variant_name: Option<String>,\n}\n\npub fn variant_box(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        props,\n        mut named_slots,\n        ..\n    } = context;\n\n    let VariantBoxProps { variant_name } = props.read_cloned_or_default();\n\n    if let Some(variant_name) = variant_name {\n        named_slots.remove(&variant_name).unwrap_or_default()\n    } else {\n        Default::default()\n    }\n}\n"
  },
  {
    "path": "crates/core/src/widget/component/containers/vertical_box.rs",
    "content": "use crate::{\n    PropsData, Scalar, make_widget, pre_hooks,\n    widget::{\n        component::{\n            containers::flex_box::{FlexBoxProps, flex_box},\n            interactive::navigation::{\n                NavContainerActive, NavItemActive, NavJumpActive, use_nav_container_active,\n                use_nav_item, use_nav_jump_vertical_step_active,\n            },\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::flex::{FlexBoxDirection, FlexBoxItemLayout},\n        utils::Transform,\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct VerticalBoxProps {\n    #[serde(default)]\n    pub separation: Scalar,\n    #[serde(default)]\n    pub reversed: bool,\n    #[serde(default)]\n    pub override_slots_layout: Option<FlexBoxItemLayout>,\n    #[serde(default)]\n    pub transform: Transform,\n}\n\n#[pre_hooks(\n    use_nav_container_active,\n    use_nav_jump_vertical_step_active,\n    use_nav_item\n)]\npub fn nav_vertical_box(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        key,\n        props,\n        listed_slots,\n        ..\n    } = context;\n\n    let props = props\n        .clone()\n        .without::<NavContainerActive>()\n        .without::<NavJumpActive>()\n        .without::<NavItemActive>();\n\n    make_widget!(vertical_box)\n        .key(key)\n        .merge_props(props)\n        .listed_slots(listed_slots)\n        .into()\n}\n\npub fn vertical_box(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        key,\n        props,\n        mut listed_slots,\n        ..\n    } = context;\n\n    let VerticalBoxProps {\n        separation,\n        reversed,\n        override_slots_layout,\n        transform,\n    } = props.read_cloned_or_default();\n\n    if let Some(layout) = override_slots_layout {\n        for slot in &mut listed_slots {\n            if let Some(props) = slot.props_mut() {\n                props.write(layout.to_owned());\n            }\n        }\n    }\n\n    let props = props.clone().with(FlexBoxProps {\n        direction: if reversed {\n            FlexBoxDirection::VerticalBottomToTop\n        } else {\n            FlexBoxDirection::VerticalTopToBottom\n        },\n        separation,\n        wrap: false,\n        transform,\n    });\n\n    make_widget!(flex_box)\n        .key(key)\n        .merge_props(props)\n        .listed_slots(listed_slots)\n        .into()\n}\n"
  },
  {
    "path": "crates/core/src/widget/component/containers/wrap_box.rs",
    "content": "use crate::{\n    PropsData, unpack_named_slots,\n    widget::{\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::size::{SizeBoxNode, SizeBoxSizeValue},\n        utils::Rect,\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct WrapBoxProps {\n    #[serde(default)]\n    pub margin: Rect,\n    #[serde(default = \"WrapBoxProps::default_fill\")]\n    pub fill: bool,\n}\n\nimpl Default for WrapBoxProps {\n    fn default() -> Self {\n        Self {\n            margin: Default::default(),\n            fill: Self::default_fill(),\n        }\n    }\n}\n\nimpl WrapBoxProps {\n    fn default_fill() -> bool {\n        true\n    }\n}\n\npub fn wrap_box(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        id,\n        props,\n        named_slots,\n        ..\n    } = context;\n    unpack_named_slots!(named_slots => content);\n\n    let WrapBoxProps { margin, fill } = props.read_cloned_or_default();\n    let (width, height) = if fill {\n        (SizeBoxSizeValue::Fill, SizeBoxSizeValue::Fill)\n    } else {\n        (SizeBoxSizeValue::Content, SizeBoxSizeValue::Content)\n    };\n\n    SizeBoxNode {\n        id: id.to_owned(),\n        props: props.clone(),\n        slot: Box::new(content),\n        margin,\n        width,\n        height,\n        ..Default::default()\n    }\n    .into()\n}\n"
  },
  {
    "path": "crates/core/src/widget/component/image_box.rs",
    "content": "use crate::{\n    PropsData,\n    widget::{\n        component::WidgetAlpha,\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::image::{\n            ImageBoxAspectRatio, ImageBoxColor, ImageBoxImage, ImageBoxMaterial, ImageBoxNode,\n            ImageBoxSizeValue,\n        },\n        utils::{Color, Transform},\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct ImageBoxProps {\n    #[serde(default)]\n    pub width: ImageBoxSizeValue,\n    #[serde(default)]\n    pub height: ImageBoxSizeValue,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub content_keep_aspect_ratio: Option<ImageBoxAspectRatio>,\n    #[serde(default)]\n    pub material: ImageBoxMaterial,\n    #[serde(default)]\n    pub transform: Transform,\n}\n\nimpl ImageBoxProps {\n    pub fn colored(color: Color) -> Self {\n        Self {\n            material: ImageBoxMaterial::Color(ImageBoxColor {\n                color,\n                ..Default::default()\n            }),\n            ..Default::default()\n        }\n    }\n\n    pub fn image(id: impl ToString) -> Self {\n        Self {\n            material: ImageBoxMaterial::Image(ImageBoxImage {\n                id: id.to_string(),\n                ..Default::default()\n            }),\n            ..Default::default()\n        }\n    }\n\n    pub fn image_aspect_ratio(id: impl ToString, outside: bool) -> Self {\n        Self {\n            material: ImageBoxMaterial::Image(ImageBoxImage {\n                id: id.to_string(),\n                ..Default::default()\n            }),\n            content_keep_aspect_ratio: Some(ImageBoxAspectRatio {\n                horizontal_alignment: 0.5,\n                vertical_alignment: 0.5,\n                outside,\n            }),\n            ..Default::default()\n        }\n    }\n}\n\npub fn image_box(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        id,\n        props,\n        shared_props,\n        ..\n    } = context;\n\n    let ImageBoxProps {\n        width,\n        height,\n        content_keep_aspect_ratio,\n        mut material,\n        transform,\n    } = props.read_cloned_or_default();\n\n    let alpha = shared_props.read_cloned_or_default::<WidgetAlpha>().0;\n    match &mut material {\n        ImageBoxMaterial::Color(image) => {\n            image.color.a *= alpha;\n        }\n        ImageBoxMaterial::Image(image) => {\n            image.tint.a *= alpha;\n        }\n        _ => {}\n    }\n\n    ImageBoxNode {\n        id: id.to_owned(),\n        props: props.clone(),\n        width,\n        height,\n        content_keep_aspect_ratio,\n        material,\n        transform,\n    }\n    .into()\n}\n"
  },
  {
    "path": "crates/core/src/widget/component/interactive/button.rs",
    "content": "use crate::{\n    MessageData, PropsData, pre_hooks, unpack_named_slots,\n    widget::{\n        WidgetId, WidgetIdOrRef,\n        component::interactive::navigation::{\n            NavSignal, use_nav_button, use_nav_item, use_nav_tracking, use_nav_tracking_self,\n        },\n        context::{WidgetContext, WidgetMountOrChangeContext},\n        node::WidgetNode,\n        unit::area::AreaBoxNode,\n    },\n};\nuse serde::{Deserialize, Serialize};\n\nfn is_false(v: &bool) -> bool {\n    !*v\n}\n\n#[derive(PropsData, Debug, Default, Copy, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct ButtonProps {\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"is_false\")]\n    pub selected: bool,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"is_false\")]\n    pub trigger: bool,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"is_false\")]\n    pub context: bool,\n}\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct ButtonNotifyProps(\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"WidgetIdOrRef::is_none\")]\n    pub WidgetIdOrRef,\n);\n\n#[derive(MessageData, Debug, Default, Clone)]\n#[message_data(crate::messenger::MessageData)]\npub struct ButtonNotifyMessage {\n    pub sender: WidgetId,\n    pub state: ButtonProps,\n    pub prev: ButtonProps,\n}\n\nimpl ButtonNotifyMessage {\n    pub fn select_start(&self) -> bool {\n        !self.prev.selected && self.state.selected\n    }\n\n    pub fn select_stop(&self) -> bool {\n        self.prev.selected && !self.state.selected\n    }\n\n    pub fn select_changed(&self) -> bool {\n        self.prev.selected != self.state.selected\n    }\n\n    pub fn trigger_start(&self) -> bool {\n        !self.prev.trigger && self.state.trigger\n    }\n\n    pub fn trigger_stop(&self) -> bool {\n        self.prev.trigger && !self.state.trigger\n    }\n\n    pub fn trigger_changed(&self) -> bool {\n        self.prev.trigger != self.state.trigger\n    }\n\n    pub fn context_start(&self) -> bool {\n        !self.prev.context && self.state.context\n    }\n\n    pub fn context_stop(&self) -> bool {\n        self.prev.context && !self.state.context\n    }\n\n    pub fn context_changed(&self) -> bool {\n        self.prev.context != self.state.context\n    }\n}\n\npub fn use_button_notified_state(context: &mut WidgetContext) {\n    context.life_cycle.change(|context| {\n        for msg in context.messenger.messages {\n            if let Some(msg) = msg.as_any().downcast_ref::<ButtonNotifyMessage>() {\n                let _ = context.state.write_with(msg.state);\n            }\n        }\n    });\n}\n\n#[pre_hooks(use_nav_item, use_nav_button)]\npub fn use_button(context: &mut WidgetContext) {\n    fn notify(context: &WidgetMountOrChangeContext, data: ButtonNotifyMessage) {\n        if let Ok(ButtonNotifyProps(notify)) = context.props.read()\n            && let Some(to) = notify.read()\n        {\n            context.messenger.write(to, data);\n        }\n    }\n\n    context.life_cycle.mount(|context| {\n        notify(\n            &context,\n            ButtonNotifyMessage {\n                sender: context.id.to_owned(),\n                state: Default::default(),\n                prev: Default::default(),\n            },\n        );\n        let _ = context.state.write_with(ButtonProps::default());\n    });\n\n    context.life_cycle.change(|context| {\n        let mut dirty = false;\n        let mut data = context.state.read_cloned_or_default::<ButtonProps>();\n        let prev = data;\n        for msg in context.messenger.messages {\n            if let Some(msg) = msg.as_any().downcast_ref() {\n                match msg {\n                    NavSignal::Select(_) => {\n                        data.selected = true;\n                        dirty = true;\n                    }\n                    NavSignal::Unselect => {\n                        data.selected = false;\n                        dirty = true;\n                    }\n                    NavSignal::Accept(v) => {\n                        data.trigger = *v;\n                        dirty = true;\n                    }\n                    NavSignal::Context(v) => {\n                        data.context = *v;\n                        dirty = true;\n                    }\n                    _ => {}\n                }\n            }\n        }\n        if dirty {\n            notify(\n                &context,\n                ButtonNotifyMessage {\n                    sender: context.id.to_owned(),\n                    state: data.to_owned(),\n                    prev,\n                },\n            );\n            let _ = context.state.write_with(data);\n        }\n    });\n}\n\n#[pre_hooks(use_button)]\npub fn button(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        id,\n        state,\n        named_slots,\n        ..\n    } = context;\n    unpack_named_slots!(named_slots => content);\n\n    if let Some(p) = content.props_mut() {\n        p.write(state.read_cloned_or_default::<ButtonProps>());\n    }\n\n    AreaBoxNode {\n        id: id.to_owned(),\n        slot: Box::new(content),\n    }\n    .into()\n}\n\n#[pre_hooks(use_nav_tracking)]\npub fn tracked_button(mut context: WidgetContext) -> WidgetNode {\n    button(context)\n}\n\n#[pre_hooks(use_nav_tracking_self)]\npub fn self_tracked_button(mut context: WidgetContext) -> WidgetNode {\n    button(context)\n}\n"
  },
  {
    "path": "crates/core/src/widget/component/interactive/float_view.rs",
    "content": "use crate::{\n    pre_hooks, unpack_named_slots,\n    widget::{\n        component::{\n            containers::float_box::{\n                FloatBoxChange, FloatBoxChangeMessage, FloatBoxNotifyProps, FloatBoxState,\n            },\n            interactive::{\n                button::{ButtonNotifyMessage, ButtonNotifyProps, ButtonProps, use_button},\n                navigation::{\n                    NavSignal, NavTrackingNotifyMessage, NavTrackingNotifyProps, use_nav_item,\n                    use_nav_tracking_self,\n                },\n            },\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::area::AreaBoxNode,\n        utils::Vec2,\n    },\n};\n\n#[pre_hooks(use_button, use_nav_tracking_self)]\npub fn use_float_view_control(context: &mut WidgetContext) {\n    context\n        .props\n        .write(ButtonNotifyProps(context.id.to_owned().into()));\n    context\n        .props\n        .write(NavTrackingNotifyProps(context.id.to_owned().into()));\n\n    context.life_cycle.unmount(|context| {\n        context.signals.write(NavSignal::Unlock);\n    });\n\n    context.life_cycle.change(|context| {\n        let Some(notify) = context\n            .props\n            .read_cloned_or_default::<FloatBoxNotifyProps>()\n            .0\n            .read()\n        else {\n            return;\n        };\n        let button = context.state.read_cloned_or_default::<ButtonProps>();\n        let zoom = context.props.read_cloned_or_default::<FloatBoxState>().zoom;\n        let scale = if zoom > 0.0 { 1.0 / zoom } else { 1.0 };\n\n        for msg in context.messenger.messages {\n            if let Some(msg) = msg.as_any().downcast_ref::<ButtonNotifyMessage>() {\n                if msg.trigger_start() {\n                    context.signals.write(NavSignal::Lock);\n                }\n                if msg.trigger_stop() {\n                    context.signals.write(NavSignal::Unlock);\n                }\n            } else if let Some(msg) = msg.as_any().downcast_ref::<NavTrackingNotifyMessage>()\n                && button.selected\n                && button.trigger\n            {\n                let delta = msg.pointer_delta_ui_space();\n                context.messenger.write(\n                    notify.clone(),\n                    FloatBoxChangeMessage {\n                        sender: context.id.to_owned(),\n                        change: FloatBoxChange::RelativePosition(Vec2 {\n                            x: delta.x * scale,\n                            y: delta.y * scale,\n                        }),\n                    },\n                );\n            }\n        }\n    });\n}\n\n#[pre_hooks(use_nav_item, use_float_view_control)]\npub fn float_view_control(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        id,\n        props,\n        state,\n        named_slots,\n        ..\n    } = context;\n    unpack_named_slots!(named_slots => content);\n\n    if let Some(p) = content.props_mut() {\n        p.merge_from(props.clone());\n        p.write(state.read_cloned_or_default::<ButtonProps>());\n    }\n\n    AreaBoxNode {\n        id: id.to_owned(),\n        slot: Box::new(content),\n    }\n    .into()\n}\n"
  },
  {
    "path": "crates/core/src/widget/component/interactive/input_field.rs",
    "content": "use crate::{\n    Integer, MessageData, PropsData, Scalar, UnsignedInteger, pre_hooks, unpack_named_slots,\n    view_model::ViewModelValue,\n    widget::{\n        WidgetId, WidgetIdOrRef,\n        component::interactive::{\n            button::{ButtonProps, use_button},\n            navigation::{NavSignal, NavTextChange, use_nav_item, use_nav_text_input},\n        },\n        context::{WidgetContext, WidgetMountOrChangeContext},\n        node::WidgetNode,\n        unit::area::AreaBoxNode,\n    },\n};\nuse intuicio_data::managed::ManagedLazy;\nuse serde::{Deserialize, Serialize};\nuse std::str::FromStr;\n\nfn is_false(v: &bool) -> bool {\n    !*v\n}\n\nfn is_zero(v: &usize) -> bool {\n    *v == 0\n}\n\npub trait TextInputProxy: Send + Sync {\n    fn get(&self) -> String;\n    fn set(&mut self, value: String);\n}\n\nimpl<T> TextInputProxy for T\nwhere\n    T: ToString + FromStr + Send + Sync,\n{\n    fn get(&self) -> String {\n        self.to_string()\n    }\n\n    fn set(&mut self, value: String) {\n        if let Ok(value) = value.parse() {\n            *self = value;\n        }\n    }\n}\n\nimpl<T> TextInputProxy for ViewModelValue<T>\nwhere\n    T: ToString + FromStr + Send + Sync,\n{\n    fn get(&self) -> String {\n        self.to_string()\n    }\n\n    fn set(&mut self, value: String) {\n        if let Ok(value) = value.parse() {\n            **self = value;\n        }\n    }\n}\n\n#[derive(Clone)]\npub struct TextInput(ManagedLazy<dyn TextInputProxy>);\n\nimpl TextInput {\n    pub fn new(data: ManagedLazy<impl TextInputProxy + 'static>) -> Self {\n        let (lifetime, data) = data.into_inner();\n        let data = data as *mut dyn TextInputProxy;\n        unsafe { Self(ManagedLazy::<dyn TextInputProxy>::new_raw(data, lifetime).unwrap()) }\n    }\n\n    pub fn into_inner(self) -> ManagedLazy<dyn TextInputProxy> {\n        self.0\n    }\n\n    pub fn get(&self) -> String {\n        self.0.read().map(|data| data.get()).unwrap_or_default()\n    }\n\n    pub fn set(&mut self, value: impl ToString) {\n        if let Some(mut data) = self.0.write() {\n            data.set(value.to_string());\n        }\n    }\n}\n\nimpl std::fmt::Debug for TextInput {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_tuple(\"TextInput\")\n            .field(&self.0.read().map(|data| data.get()).unwrap_or_default())\n            .finish()\n    }\n}\n\nimpl<T: TextInputProxy + 'static> From<ManagedLazy<T>> for TextInput {\n    fn from(value: ManagedLazy<T>) -> Self {\n        Self::new(value)\n    }\n}\n\n#[derive(PropsData, Debug, Default, Clone, Copy, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub enum TextInputMode {\n    #[default]\n    Text,\n    Number,\n    Integer,\n    UnsignedInteger,\n    #[serde(skip)]\n    Filter(fn(usize, char) -> bool),\n}\n\nimpl TextInputMode {\n    pub fn is_text(&self) -> bool {\n        matches!(self, Self::Text)\n    }\n\n    pub fn is_number(&self) -> bool {\n        matches!(self, Self::Number)\n    }\n\n    pub fn is_integer(&self) -> bool {\n        matches!(self, Self::Integer)\n    }\n\n    pub fn is_unsigned_integer(&self) -> bool {\n        matches!(self, Self::UnsignedInteger)\n    }\n\n    pub fn is_filter(&self) -> bool {\n        matches!(self, Self::Filter(_))\n    }\n\n    pub fn process(&self, text: &str) -> Option<String> {\n        match self {\n            Self::Text => Some(text.to_owned()),\n            Self::Number => text.parse::<Scalar>().ok().map(|v| v.to_string()),\n            Self::Integer => text.parse::<Integer>().ok().map(|v| v.to_string()),\n            Self::UnsignedInteger => text.parse::<UnsignedInteger>().ok().map(|v| v.to_string()),\n            Self::Filter(f) => {\n                if text.char_indices().any(|(i, c)| !f(i, c)) {\n                    None\n                } else {\n                    Some(text.to_owned())\n                }\n            }\n        }\n    }\n\n    pub fn is_valid(&self, text: &str) -> bool {\n        match self {\n            Self::Text => true,\n            Self::Number => text.parse::<Scalar>().is_ok() || text == \"-\",\n            Self::Integer => text.parse::<Integer>().is_ok() || text == \"-\",\n            Self::UnsignedInteger => text.parse::<UnsignedInteger>().is_ok(),\n            Self::Filter(f) => text.char_indices().all(|(i, c)| f(i, c)),\n        }\n    }\n}\n\n#[derive(PropsData, Debug, Default, Clone, Copy, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct TextInputState {\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"is_false\")]\n    pub focused: bool,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"is_zero\")]\n    pub cursor_position: usize,\n}\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct TextInputProps {\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"is_false\")]\n    pub allow_new_line: bool,\n    #[serde(default)]\n    #[serde(skip)]\n    pub text: Option<TextInput>,\n}\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct TextInputNotifyProps(\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"WidgetIdOrRef::is_none\")]\n    pub WidgetIdOrRef,\n);\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct TextInputControlNotifyProps(\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"WidgetIdOrRef::is_none\")]\n    pub WidgetIdOrRef,\n);\n\n#[derive(MessageData, Debug, Clone)]\n#[message_data(crate::messenger::MessageData)]\npub struct TextInputNotifyMessage {\n    pub sender: WidgetId,\n    pub state: TextInputState,\n    pub submitted: bool,\n}\n\n#[derive(MessageData, Debug, Clone)]\n#[message_data(crate::messenger::MessageData)]\npub struct TextInputControlNotifyMessage {\n    pub sender: WidgetId,\n    pub character: char,\n}\n\npub fn use_text_input_notified_state(context: &mut WidgetContext) {\n    context.life_cycle.change(|context| {\n        for msg in context.messenger.messages {\n            if let Some(msg) = msg.as_any().downcast_ref::<TextInputNotifyMessage>() {\n                let _ = context.state.write_with(msg.state.to_owned());\n            }\n        }\n    });\n}\n\n#[pre_hooks(use_nav_text_input)]\npub fn use_text_input(context: &mut WidgetContext) {\n    fn notify(context: &WidgetMountOrChangeContext, data: TextInputNotifyMessage) {\n        if let Ok(notify) = context.props.read::<TextInputNotifyProps>()\n            && let Some(to) = notify.0.read()\n        {\n            context.messenger.write(to, data);\n        }\n    }\n\n    context.life_cycle.mount(|context| {\n        notify(\n            &context,\n            TextInputNotifyMessage {\n                sender: context.id.to_owned(),\n                state: Default::default(),\n                submitted: false,\n            },\n        );\n        let _ = context.state.write_with(TextInputState::default());\n    });\n\n    context.life_cycle.change(|context| {\n        let mode = context.props.read_cloned_or_default::<TextInputMode>();\n        let mut props = context.props.read_cloned_or_default::<TextInputProps>();\n        let mut state = context.state.read_cloned_or_default::<TextInputState>();\n        let mut text = props\n            .text\n            .as_ref()\n            .map(|text| text.get())\n            .unwrap_or_default();\n        let mut dirty_text = false;\n        let mut dirty_state = false;\n        let mut submitted = false;\n        for msg in context.messenger.messages {\n            if let Some(msg) = msg.as_any().downcast_ref() {\n                match msg {\n                    NavSignal::FocusTextInput(idref) => {\n                        state.focused = idref.is_some();\n                        dirty_state = true;\n                    }\n                    NavSignal::TextChange(change) => {\n                        if state.focused {\n                            match change {\n                                NavTextChange::InsertCharacter(c) => {\n                                    if c.is_control() {\n                                        if let Ok(notify) =\n                                            context.props.read::<TextInputControlNotifyProps>()\n                                            && let Some(to) = notify.0.read()\n                                        {\n                                            context.messenger.write(\n                                                to,\n                                                TextInputControlNotifyMessage {\n                                                    sender: context.id.to_owned(),\n                                                    character: *c,\n                                                },\n                                            );\n                                        }\n                                    } else {\n                                        state.cursor_position =\n                                            state.cursor_position.min(text.chars().count());\n                                        let mut iter = text.chars();\n                                        let mut new_text = iter\n                                            .by_ref()\n                                            .take(state.cursor_position)\n                                            .collect::<String>();\n                                        new_text.push(*c);\n                                        new_text.extend(iter);\n                                        if mode.is_valid(&new_text) {\n                                            state.cursor_position += 1;\n                                            text = new_text;\n                                            dirty_text = true;\n                                            dirty_state = true;\n                                        }\n                                    }\n                                }\n                                NavTextChange::MoveCursorLeft => {\n                                    if state.cursor_position > 0 {\n                                        state.cursor_position -= 1;\n                                        dirty_state = true;\n                                    }\n                                }\n                                NavTextChange::MoveCursorRight => {\n                                    if state.cursor_position < text.chars().count() {\n                                        state.cursor_position += 1;\n                                        dirty_state = true;\n                                    }\n                                }\n                                NavTextChange::MoveCursorStart => {\n                                    state.cursor_position = 0;\n                                    dirty_state = true;\n                                }\n                                NavTextChange::MoveCursorEnd => {\n                                    state.cursor_position = text.chars().count();\n                                    dirty_state = true;\n                                }\n                                NavTextChange::DeleteLeft => {\n                                    if state.cursor_position > 0 {\n                                        let mut iter = text.chars();\n                                        let mut new_text = iter\n                                            .by_ref()\n                                            .take(state.cursor_position - 1)\n                                            .collect::<String>();\n                                        iter.by_ref().next();\n                                        new_text.extend(iter);\n                                        if mode.is_valid(&new_text) {\n                                            state.cursor_position -= 1;\n                                            text = new_text;\n                                            dirty_text = true;\n                                            dirty_state = true;\n                                        }\n                                    }\n                                }\n                                NavTextChange::DeleteRight => {\n                                    let mut iter = text.chars();\n                                    let mut new_text = iter\n                                        .by_ref()\n                                        .take(state.cursor_position)\n                                        .collect::<String>();\n                                    iter.by_ref().next();\n                                    new_text.extend(iter);\n                                    if mode.is_valid(&new_text) {\n                                        text = new_text;\n                                        dirty_text = true;\n                                        dirty_state = true;\n                                    }\n                                }\n                                NavTextChange::NewLine => {\n                                    if props.allow_new_line {\n                                        let mut iter = text.chars();\n                                        let mut new_text = iter\n                                            .by_ref()\n                                            .take(state.cursor_position)\n                                            .collect::<String>();\n                                        new_text.push('\\n');\n                                        new_text.extend(iter);\n                                        if mode.is_valid(&new_text) {\n                                            state.cursor_position += 1;\n                                            text = new_text;\n                                            dirty_text = true;\n                                            dirty_state = true;\n                                        }\n                                    } else {\n                                        submitted = true;\n                                        dirty_state = true;\n                                    }\n                                }\n                            }\n                        }\n                    }\n                    _ => {}\n                }\n            }\n        }\n        if dirty_state {\n            state.cursor_position = state.cursor_position.min(text.chars().count());\n            notify(\n                &context,\n                TextInputNotifyMessage {\n                    sender: context.id.to_owned(),\n                    state,\n                    submitted,\n                },\n            );\n            let _ = context.state.write_with(state);\n        }\n        if dirty_text && let Some(data) = props.text.as_mut() {\n            data.set(text);\n            context.messenger.write(context.id.to_owned(), ());\n        }\n        if submitted {\n            context.signals.write(NavSignal::FocusTextInput(().into()));\n        }\n    });\n}\n\n#[pre_hooks(use_button, use_text_input)]\npub fn use_input_field(context: &mut WidgetContext) {\n    context.life_cycle.change(|context| {\n        let focused = context\n            .state\n            .map_or_default::<TextInputState, _, _>(|s| s.focused);\n        for msg in context.messenger.messages {\n            if let Some(msg) = msg.as_any().downcast_ref() {\n                match msg {\n                    NavSignal::Accept(true) => {\n                        if !focused {\n                            context\n                                .signals\n                                .write(NavSignal::FocusTextInput(context.id.to_owned().into()));\n                        }\n                    }\n                    NavSignal::Cancel(true) => {\n                        if focused {\n                            context.signals.write(NavSignal::FocusTextInput(().into()));\n                        }\n                    }\n                    _ => {}\n                }\n            }\n        }\n    });\n}\n\n#[pre_hooks(use_nav_item, use_text_input)]\npub fn text_input(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        id,\n        props,\n        state,\n        named_slots,\n        ..\n    } = context;\n    unpack_named_slots!(named_slots => content);\n\n    if let Some(p) = content.props_mut() {\n        p.write(state.read_cloned_or_default::<TextInputState>());\n        p.write(props.read_cloned_or_default::<TextInputProps>());\n    }\n\n    AreaBoxNode {\n        id: id.to_owned(),\n        slot: Box::new(content),\n    }\n    .into()\n}\n\n#[pre_hooks(use_nav_item, use_input_field)]\npub fn input_field(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        id,\n        props,\n        state,\n        named_slots,\n        ..\n    } = context;\n    unpack_named_slots!(named_slots => content);\n\n    if let Some(p) = content.props_mut() {\n        p.write(state.read_cloned_or_default::<ButtonProps>());\n        p.write(state.read_cloned_or_default::<TextInputState>());\n        p.write(props.read_cloned_or_default::<TextInputProps>());\n    }\n\n    AreaBoxNode {\n        id: id.to_owned(),\n        slot: Box::new(content),\n    }\n    .into()\n}\n\npub fn input_text_with_cursor(text: &str, position: usize, cursor: char) -> String {\n    text.chars()\n        .take(position)\n        .chain(std::iter::once(cursor))\n        .chain(text.chars().skip(position))\n        .collect()\n}\n"
  },
  {
    "path": "crates/core/src/widget/component/interactive/mod.rs",
    "content": "pub mod button;\npub mod float_view;\npub mod input_field;\npub mod navigation;\npub mod options_view;\npub mod scroll_view;\npub mod slider_view;\n"
  },
  {
    "path": "crates/core/src/widget/component/interactive/navigation.rs",
    "content": "use crate::{\n    MessageData, PropsData, Scalar, post_hooks, pre_hooks, unpack_named_slots,\n    widget::{\n        WidgetId, WidgetIdOrRef, component::containers::portal_box::PortalsContainer,\n        context::WidgetContext, node::WidgetNode, unit::area::AreaBoxNode, utils::Vec2,\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct NavAutoSelect;\n\n#[derive(PropsData, Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct NavItemActive;\n\n#[derive(PropsData, Debug, Default, Clone, PartialEq, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct NavTrackingActive(#[serde(default)] pub WidgetIdOrRef);\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct NavTrackingNotifyProps(\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"WidgetIdOrRef::is_none\")]\n    pub WidgetIdOrRef,\n);\n\n#[derive(PropsData, Debug, Default, Clone, Copy, PartialEq, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct NavTrackingProps {\n    #[serde(default)]\n    pub factor: Vec2,\n    #[serde(default)]\n    pub unscaled: Vec2,\n    #[serde(default)]\n    pub ui_space: Vec2,\n}\n\n#[derive(MessageData, Debug, Default, Clone)]\n#[message_data(crate::messenger::MessageData)]\npub struct NavTrackingNotifyMessage {\n    pub sender: WidgetId,\n    pub state: NavTrackingProps,\n    pub prev: NavTrackingProps,\n}\n\nimpl NavTrackingNotifyMessage {\n    pub fn pointer_delta_factor(&self) -> Vec2 {\n        Vec2 {\n            x: self.state.factor.x - self.prev.factor.x,\n            y: self.state.factor.y - self.prev.factor.y,\n        }\n    }\n\n    pub fn pointer_delta_unscaled(&self) -> Vec2 {\n        Vec2 {\n            x: self.state.unscaled.x - self.prev.unscaled.x,\n            y: self.state.unscaled.y - self.prev.unscaled.y,\n        }\n    }\n\n    pub fn pointer_delta_ui_space(&self) -> Vec2 {\n        Vec2 {\n            x: self.state.ui_space.x - self.prev.ui_space.x,\n            y: self.state.ui_space.y - self.prev.ui_space.y,\n        }\n    }\n\n    pub fn pointer_moved(&self) -> bool {\n        (self.state.factor.x - self.prev.factor.x) + (self.state.factor.y - self.prev.factor.y)\n            > 1.0e-6\n    }\n}\n\n#[derive(PropsData, Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct NavLockingActive;\n\n#[derive(PropsData, Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct NavContainerActive;\n\n#[derive(PropsData, Debug, Clone, PartialEq, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct NavContainerDesiredSelection(#[serde(default)] pub WidgetIdOrRef);\n\n#[derive(PropsData, Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct NavJumpActive(#[serde(default)] pub NavJumpMode);\n\n#[derive(PropsData, Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct NavJumpLooped;\n\n#[derive(Debug, Clone, PartialEq)]\npub enum NavType {\n    Container,\n    Item,\n    Button,\n    TextInput,\n    ScrollView,\n    ScrollViewContent,\n    /// (tracked widget)\n    Tracking(WidgetIdOrRef),\n}\n\n#[derive(MessageData, Debug, Default, Clone)]\n#[message_data(crate::messenger::MessageData)]\npub enum NavSignal {\n    #[default]\n    None,\n    Register(NavType),\n    Unregister(NavType),\n    Select(WidgetIdOrRef),\n    Unselect,\n    Lock,\n    Unlock,\n    Accept(bool),\n    Context(bool),\n    Cancel(bool),\n    Up,\n    Down,\n    Left,\n    Right,\n    Prev,\n    Next,\n    Jump(NavJump),\n    FocusTextInput(WidgetIdOrRef),\n    TextChange(NavTextChange),\n    Axis(String, Scalar),\n    Custom(WidgetIdOrRef, String),\n}\n\n#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]\npub enum NavJumpMode {\n    #[default]\n    Direction,\n    StepHorizontal,\n    StepVertical,\n    StepPages,\n}\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct NavJumpMapProps {\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"WidgetIdOrRef::is_none\")]\n    pub up: WidgetIdOrRef,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"WidgetIdOrRef::is_none\")]\n    pub down: WidgetIdOrRef,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"WidgetIdOrRef::is_none\")]\n    pub left: WidgetIdOrRef,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"WidgetIdOrRef::is_none\")]\n    pub right: WidgetIdOrRef,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"WidgetIdOrRef::is_none\")]\n    pub prev: WidgetIdOrRef,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"WidgetIdOrRef::is_none\")]\n    pub next: WidgetIdOrRef,\n}\n\n#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]\npub enum NavDirection {\n    #[default]\n    None,\n    Up,\n    Down,\n    Left,\n    Right,\n    Prev,\n    Next,\n}\n\n#[derive(Debug, Clone)]\npub enum NavJump {\n    First,\n    Last,\n    TopLeft,\n    TopRight,\n    BottomLeft,\n    BottomRight,\n    MiddleCenter,\n    Loop(NavDirection),\n    Escape(NavDirection, WidgetIdOrRef),\n    Scroll(NavScroll),\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]\npub enum NavTextChange {\n    InsertCharacter(char),\n    MoveCursorLeft,\n    MoveCursorRight,\n    MoveCursorStart,\n    MoveCursorEnd,\n    DeleteLeft,\n    DeleteRight,\n    NewLine,\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub enum NavScroll {\n    /// (factor location, relative)\n    Factor(Vec2, bool),\n    /// (scroll view id or ref, factor location, relative)\n    DirectFactor(WidgetIdOrRef, Vec2, bool),\n    /// (local space units location, relative)\n    Units(Vec2, bool),\n    /// (scroll view id or ref, local space units location, relative)\n    DirectUnits(WidgetIdOrRef, Vec2, bool),\n    /// (id or ref, widget local space anchor point)\n    Widget(WidgetIdOrRef, Vec2),\n    /// (factor, content to container ratio, relative)\n    Change(Vec2, Vec2, bool),\n}\n\npub fn use_nav_container(context: &mut WidgetContext) {\n    context.life_cycle.mount(|context| {\n        if context.props.has::<NavContainerActive>() {\n            context\n                .signals\n                .write(NavSignal::Register(NavType::Container));\n        }\n        if context.props.has::<NavAutoSelect>() {\n            context\n                .signals\n                .write(NavSignal::Select(context.id.to_owned().into()));\n        }\n    });\n\n    context.life_cycle.unmount(|context| {\n        context\n            .signals\n            .write(NavSignal::Unregister(NavType::Container));\n    });\n\n    context.life_cycle.change(move |context| {\n        if let Ok(props) = context.props.read::<NavContainerDesiredSelection>() {\n            for msg in context.messenger.messages {\n                if let Some(NavSignal::Select(idref)) = msg.as_any().downcast_ref::<NavSignal>()\n                    && idref.read().map(|id| &id == context.id).unwrap_or_default()\n                {\n                    context.signals.write(NavSignal::Select(props.0.to_owned()));\n                }\n            }\n        }\n    });\n}\n\n#[post_hooks(use_nav_container)]\npub fn use_nav_container_active(context: &mut WidgetContext) {\n    context.props.write(NavContainerActive);\n}\n\npub fn use_nav_jump_map(context: &mut WidgetContext) {\n    if !context.props.has::<NavJumpActive>() {\n        return;\n    }\n\n    context.life_cycle.change(|context| {\n        let jump = match context.props.read::<NavJumpMapProps>() {\n            Ok(jump) => jump,\n            _ => return,\n        };\n        for msg in context.messenger.messages {\n            if let Some(msg) = msg.as_any().downcast_ref() {\n                match msg {\n                    NavSignal::Up => {\n                        if jump.up.is_some() {\n                            context.signals.write(NavSignal::Select(jump.up.to_owned()));\n                        }\n                    }\n                    NavSignal::Down => {\n                        if jump.down.is_some() {\n                            context\n                                .signals\n                                .write(NavSignal::Select(jump.down.to_owned()));\n                        }\n                    }\n                    NavSignal::Left => {\n                        if jump.left.is_some() {\n                            context\n                                .signals\n                                .write(NavSignal::Select(jump.left.to_owned()));\n                        }\n                    }\n                    NavSignal::Right => {\n                        if jump.right.is_some() {\n                            context\n                                .signals\n                                .write(NavSignal::Select(jump.right.to_owned()));\n                        }\n                    }\n                    NavSignal::Prev => {\n                        if jump.prev.is_some() {\n                            context\n                                .signals\n                                .write(NavSignal::Select(jump.prev.to_owned()));\n                        }\n                    }\n                    NavSignal::Next => {\n                        if jump.next.is_some() {\n                            context\n                                .signals\n                                .write(NavSignal::Select(jump.next.to_owned()));\n                        }\n                    }\n                    _ => {}\n                }\n            }\n        }\n    });\n}\n\npub fn use_nav_jump(context: &mut WidgetContext) {\n    context.life_cycle.change(|context| {\n        let mode = match context.props.read::<NavJumpActive>() {\n            Ok(data) => data.0,\n            Err(_) => return,\n        };\n        let looped = context.props.has::<NavJumpLooped>();\n        let jump = context.props.read_cloned_or_default::<NavJumpMapProps>();\n        for msg in context.messenger.messages {\n            if let Some(msg) = msg.as_any().downcast_ref() {\n                match (mode, msg) {\n                    (NavJumpMode::Direction, NavSignal::Up) => {\n                        if looped {\n                            context\n                                .signals\n                                .write(NavSignal::Jump(NavJump::Loop(NavDirection::Up)));\n                        } else {\n                            context.signals.write(NavSignal::Jump(NavJump::Escape(\n                                NavDirection::Up,\n                                jump.up.to_owned(),\n                            )));\n                        }\n                    }\n                    (NavJumpMode::Direction, NavSignal::Down) => {\n                        if looped {\n                            context\n                                .signals\n                                .write(NavSignal::Jump(NavJump::Loop(NavDirection::Down)));\n                        } else {\n                            context.signals.write(NavSignal::Jump(NavJump::Escape(\n                                NavDirection::Down,\n                                jump.down.to_owned(),\n                            )));\n                        }\n                    }\n                    (NavJumpMode::Direction, NavSignal::Left) => {\n                        if looped {\n                            context\n                                .signals\n                                .write(NavSignal::Jump(NavJump::Loop(NavDirection::Left)));\n                        } else {\n                            context.signals.write(NavSignal::Jump(NavJump::Escape(\n                                NavDirection::Left,\n                                jump.left.to_owned(),\n                            )));\n                        }\n                    }\n                    (NavJumpMode::Direction, NavSignal::Right) => {\n                        if looped {\n                            context\n                                .signals\n                                .write(NavSignal::Jump(NavJump::Loop(NavDirection::Right)));\n                        } else {\n                            context.signals.write(NavSignal::Jump(NavJump::Escape(\n                                NavDirection::Right,\n                                jump.right.to_owned(),\n                            )));\n                        }\n                    }\n                    (NavJumpMode::StepHorizontal, NavSignal::Left) => {\n                        if looped {\n                            context\n                                .signals\n                                .write(NavSignal::Jump(NavJump::Loop(NavDirection::Prev)));\n                        } else {\n                            context.signals.write(NavSignal::Jump(NavJump::Escape(\n                                NavDirection::Prev,\n                                jump.left.to_owned(),\n                            )));\n                        }\n                    }\n                    (NavJumpMode::StepHorizontal, NavSignal::Right) => {\n                        if looped {\n                            context\n                                .signals\n                                .write(NavSignal::Jump(NavJump::Loop(NavDirection::Next)));\n                        } else {\n                            context.signals.write(NavSignal::Jump(NavJump::Escape(\n                                NavDirection::Next,\n                                jump.right.to_owned(),\n                            )));\n                        }\n                    }\n                    (NavJumpMode::StepVertical, NavSignal::Up) => {\n                        if looped {\n                            context\n                                .signals\n                                .write(NavSignal::Jump(NavJump::Loop(NavDirection::Prev)));\n                        } else {\n                            context.signals.write(NavSignal::Jump(NavJump::Escape(\n                                NavDirection::Prev,\n                                jump.up.to_owned(),\n                            )));\n                        }\n                    }\n                    (NavJumpMode::StepVertical, NavSignal::Down) => {\n                        if looped {\n                            context\n                                .signals\n                                .write(NavSignal::Jump(NavJump::Loop(NavDirection::Next)));\n                        } else {\n                            context.signals.write(NavSignal::Jump(NavJump::Escape(\n                                NavDirection::Next,\n                                jump.down.to_owned(),\n                            )));\n                        }\n                    }\n                    (NavJumpMode::StepPages, NavSignal::Prev) => {\n                        if looped {\n                            context\n                                .signals\n                                .write(NavSignal::Jump(NavJump::Loop(NavDirection::Prev)));\n                        } else {\n                            context.signals.write(NavSignal::Jump(NavJump::Escape(\n                                NavDirection::Prev,\n                                jump.prev.to_owned(),\n                            )));\n                        }\n                    }\n                    (NavJumpMode::StepPages, NavSignal::Next) => {\n                        if looped {\n                            context\n                                .signals\n                                .write(NavSignal::Jump(NavJump::Loop(NavDirection::Next)));\n                        } else {\n                            context.signals.write(NavSignal::Jump(NavJump::Escape(\n                                NavDirection::Next,\n                                jump.next.to_owned(),\n                            )));\n                        }\n                    }\n                    _ => {}\n                }\n            }\n        }\n    });\n}\n\n#[post_hooks(use_nav_jump)]\npub fn use_nav_jump_direction_active(context: &mut WidgetContext) {\n    context.props.write(NavJumpActive(NavJumpMode::Direction));\n}\n\n#[post_hooks(use_nav_jump)]\npub fn use_nav_jump_horizontal_step_active(context: &mut WidgetContext) {\n    context\n        .props\n        .write(NavJumpActive(NavJumpMode::StepHorizontal));\n}\n\n#[post_hooks(use_nav_jump)]\npub fn use_nav_jump_vertical_step_active(context: &mut WidgetContext) {\n    context\n        .props\n        .write(NavJumpActive(NavJumpMode::StepVertical));\n}\n\n#[post_hooks(use_nav_jump)]\npub fn use_nav_jump_step_pages_active(context: &mut WidgetContext) {\n    context.props.write(NavJumpActive(NavJumpMode::StepPages));\n}\n\npub fn use_nav_item(context: &mut WidgetContext) {\n    context.life_cycle.mount(|context| {\n        if context.props.has::<NavItemActive>() {\n            context.signals.write(NavSignal::Register(NavType::Item));\n        }\n        if context.props.has::<NavAutoSelect>() {\n            context\n                .signals\n                .write(NavSignal::Select(context.id.to_owned().into()));\n        }\n    });\n\n    context.life_cycle.unmount(|context| {\n        context.signals.write(NavSignal::Unregister(NavType::Item));\n    });\n}\n\n#[post_hooks(use_nav_item)]\npub fn use_nav_item_active(context: &mut WidgetContext) {\n    context.props.write(NavItemActive);\n}\n\npub fn use_nav_button(context: &mut WidgetContext) {\n    context.life_cycle.mount(|context| {\n        context.signals.write(NavSignal::Register(NavType::Button));\n    });\n\n    context.life_cycle.unmount(|context| {\n        context\n            .signals\n            .write(NavSignal::Unregister(NavType::Button));\n    });\n}\n\npub fn use_nav_tracking(context: &mut WidgetContext) {\n    context.life_cycle.mount(|context| {\n        if let Ok(tracking) = context.props.read::<NavTrackingActive>() {\n            context\n                .signals\n                .write(NavSignal::Register(NavType::Tracking(tracking.0.clone())));\n            let _ = context.state.write_with(NavTrackingProps::default());\n        }\n    });\n\n    context.life_cycle.unmount(|context| {\n        context\n            .signals\n            .write(NavSignal::Unregister(NavType::Tracking(Default::default())));\n    });\n\n    context.life_cycle.change(|context| {\n        if let Ok(tracking) = context.props.read::<NavTrackingActive>() {\n            if !context.state.has::<NavTrackingProps>() {\n                context\n                    .signals\n                    .write(NavSignal::Register(NavType::Tracking(tracking.0.clone())));\n                let _ = context.state.write_with(NavTrackingProps::default());\n            }\n            let mut dirty = false;\n            let mut data = context.state.read_cloned_or_default::<NavTrackingProps>();\n            let prev = data;\n            for msg in context.messenger.messages {\n                if let Some(NavSignal::Axis(axis, value)) = msg.as_any().downcast_ref::<NavSignal>()\n                {\n                    match axis.as_str() {\n                        \"pointer-x\" => {\n                            data.factor.x = *value;\n                            dirty = true;\n                        }\n                        \"pointer-y\" => {\n                            data.factor.y = *value;\n                            dirty = true;\n                        }\n                        \"pointer-x-unscaled\" => {\n                            data.unscaled.x = *value;\n                            dirty = true;\n                        }\n                        \"pointer-y-unscaled\" => {\n                            data.unscaled.y = *value;\n                            dirty = true;\n                        }\n                        \"pointer-x-ui\" => {\n                            data.ui_space.x = *value;\n                            dirty = true;\n                        }\n                        \"pointer-y-ui\" => {\n                            data.ui_space.y = *value;\n                            dirty = true;\n                        }\n                        _ => {}\n                    }\n                }\n            }\n            if dirty {\n                if let Ok(NavTrackingNotifyProps(notify)) = context.props.read()\n                    && let Some(to) = notify.read()\n                {\n                    context.messenger.write(\n                        to,\n                        NavTrackingNotifyMessage {\n                            sender: context.id.to_owned(),\n                            state: data.to_owned(),\n                            prev,\n                        },\n                    );\n                }\n                let _ = context.state.write_with(data);\n            }\n        } else if context.state.has::<NavTrackingProps>() {\n            context\n                .signals\n                .write(NavSignal::Unregister(NavType::Tracking(Default::default())));\n            let _ = context.state.write_without::<NavTrackingProps>();\n        }\n    });\n}\n\n#[pre_hooks(use_nav_tracking)]\npub fn use_nav_tracking_self(context: &mut WidgetContext) {\n    context\n        .props\n        .write(NavTrackingActive(context.id.to_owned().into()));\n}\n\n#[pre_hooks(use_nav_tracking)]\npub fn use_nav_tracking_active_portals_container(context: &mut WidgetContext) {\n    if let Ok(data) = context.shared_props.read::<PortalsContainer>() {\n        context\n            .props\n            .write(NavTrackingActive(data.0.to_owned().into()));\n    }\n}\n\npub fn use_nav_tracking_notified_state(context: &mut WidgetContext) {\n    context.life_cycle.change(|context| {\n        for msg in context.messenger.messages {\n            if let Some(msg) = msg.as_any().downcast_ref::<NavTrackingNotifyMessage>() {\n                let _ = context.state.write_with(msg.state);\n            }\n        }\n    });\n}\n\npub fn use_nav_locking(context: &mut WidgetContext) {\n    context.life_cycle.mount(|context| {\n        if context.props.has::<NavLockingActive>() {\n            context.signals.write(NavSignal::Lock);\n            let _ = context.state.write_with(NavLockingActive);\n        }\n    });\n\n    context.life_cycle.unmount(|context| {\n        context.signals.write(NavSignal::Unlock);\n    });\n\n    context.life_cycle.change(|context| {\n        if context.props.has::<NavLockingActive>() {\n            if !context.state.has::<NavLockingActive>() {\n                context.signals.write(NavSignal::Lock);\n                let _ = context.state.write_with(NavLockingActive);\n            }\n        } else if context.state.has::<NavLockingActive>()\n            && !context.props.has::<NavLockingActive>()\n        {\n            context.signals.write(NavSignal::Unlock);\n            let _ = context.state.write_without::<NavLockingActive>();\n        }\n    });\n}\n\npub fn use_nav_text_input(context: &mut WidgetContext) {\n    context.life_cycle.mount(|context| {\n        context\n            .signals\n            .write(NavSignal::Register(NavType::TextInput));\n    });\n\n    context.life_cycle.unmount(|context| {\n        context\n            .signals\n            .write(NavSignal::Unregister(NavType::TextInput));\n    });\n}\n\npub fn use_nav_scroll_view(context: &mut WidgetContext) {\n    context.life_cycle.mount(|context| {\n        context\n            .signals\n            .write(NavSignal::Register(NavType::ScrollView));\n    });\n\n    context.life_cycle.unmount(|context| {\n        context\n            .signals\n            .write(NavSignal::Unregister(NavType::ScrollView));\n    });\n}\n\npub fn use_nav_scroll_view_content(context: &mut WidgetContext) {\n    context.life_cycle.mount(|context| {\n        context\n            .signals\n            .write(NavSignal::Register(NavType::ScrollViewContent));\n    });\n\n    context.life_cycle.unmount(|context| {\n        context\n            .signals\n            .write(NavSignal::Unregister(NavType::ScrollViewContent));\n    });\n}\n\n#[pre_hooks(use_nav_button)]\npub fn navigation_barrier(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        id, named_slots, ..\n    } = context;\n    unpack_named_slots!(named_slots => content);\n\n    AreaBoxNode {\n        id: id.to_owned(),\n        slot: Box::new(content),\n    }\n    .into()\n}\n\n#[pre_hooks(use_nav_tracking)]\npub fn tracking(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        id, named_slots, ..\n    } = context;\n    unpack_named_slots!(named_slots => content);\n\n    AreaBoxNode {\n        id: id.to_owned(),\n        slot: Box::new(content),\n    }\n    .into()\n}\n\n#[pre_hooks(use_nav_tracking_self)]\npub fn self_tracking(mut context: WidgetContext) -> WidgetNode {\n    tracking(context)\n}\n"
  },
  {
    "path": "crates/core/src/widget/component/interactive/options_view.rs",
    "content": "use crate::{\n    PropsData, make_widget, pre_hooks, unpack_named_slots,\n    view_model::ViewModelValue,\n    widget::{\n        WidgetIdMetaParams,\n        component::{\n            containers::{\n                anchor_box::PivotBoxProps,\n                context_box::{ContextBoxProps, portals_context_box},\n                size_box::{SizeBoxProps, size_box},\n            },\n            interactive::{\n                button::{ButtonNotifyMessage, ButtonNotifyProps, button},\n                navigation::NavItemActive,\n            },\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n    },\n};\nuse intuicio_data::managed::ManagedLazy;\nuse serde::{Deserialize, Serialize};\nuse std::ops::{Deref, DerefMut};\n\npub trait OptionsViewProxy: Send + Sync {\n    fn get(&self) -> usize;\n    fn set(&mut self, value: usize);\n}\n\nmacro_rules! impl_proxy {\n    ($type:ty) => {\n        impl OptionsViewProxy for $type {\n            fn get(&self) -> usize {\n                *self as _\n            }\n\n            fn set(&mut self, value: usize) {\n                *self = value as _;\n            }\n        }\n    };\n}\n\nimpl_proxy!(u8);\nimpl_proxy!(u16);\nimpl_proxy!(u32);\nimpl_proxy!(u64);\nimpl_proxy!(u128);\nimpl_proxy!(usize);\nimpl_proxy!(i8);\nimpl_proxy!(i16);\nimpl_proxy!(i32);\nimpl_proxy!(i64);\nimpl_proxy!(i128);\nimpl_proxy!(isize);\nimpl_proxy!(f32);\nimpl_proxy!(f64);\n\nimpl<T> OptionsViewProxy for ViewModelValue<T>\nwhere\n    T: OptionsViewProxy,\n{\n    fn get(&self) -> usize {\n        self.deref().get()\n    }\n\n    fn set(&mut self, value: usize) {\n        self.deref_mut().set(value);\n    }\n}\n\n#[derive(Clone)]\npub struct OptionsInput(ManagedLazy<dyn OptionsViewProxy>);\n\nimpl OptionsInput {\n    pub fn new(data: ManagedLazy<impl OptionsViewProxy + 'static>) -> Self {\n        let (lifetime, data) = data.into_inner();\n        let data = data as *mut dyn OptionsViewProxy;\n        unsafe { Self(ManagedLazy::<dyn OptionsViewProxy>::new_raw(data, lifetime).unwrap()) }\n    }\n\n    pub fn into_inner(self) -> ManagedLazy<dyn OptionsViewProxy> {\n        self.0\n    }\n\n    pub fn get<T: TryFrom<usize> + Default>(&self) -> T {\n        self.0\n            .read()\n            .map(|data| data.get())\n            .and_then(|value| T::try_from(value).ok())\n            .unwrap_or_default()\n    }\n\n    pub fn set<T: TryInto<usize>>(&mut self, value: T) {\n        if let Some(mut data) = self.0.write()\n            && let Ok(value) = value.try_into()\n        {\n            data.set(value);\n        }\n    }\n}\n\nimpl std::fmt::Debug for OptionsInput {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_tuple(\"OptionsInput\")\n            .field(&self.0.read().map(|data| data.get()).unwrap_or_default())\n            .finish()\n    }\n}\n\nimpl<T: OptionsViewProxy + 'static> From<ManagedLazy<T>> for OptionsInput {\n    fn from(value: ManagedLazy<T>) -> Self {\n        Self::new(value)\n    }\n}\n\n#[derive(PropsData, Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub enum OptionsViewMode {\n    Selected,\n    #[default]\n    Option,\n}\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct OptionsViewProps {\n    #[serde(default)]\n    #[serde(skip)]\n    pub input: Option<OptionsInput>,\n}\n\nimpl OptionsViewProps {\n    pub fn get_index(&self) -> usize {\n        self.input\n            .as_ref()\n            .map(|input| input.get::<usize>())\n            .unwrap_or_default()\n    }\n\n    pub fn set_index(&mut self, value: usize) {\n        if let Some(input) = self.input.as_mut() {\n            input.set(value);\n        }\n    }\n}\n\nfn use_options_view(context: &mut WidgetContext) {\n    context.life_cycle.change(|context| {\n        for msg in context.messenger.messages {\n            if let Some(msg) = msg.as_any().downcast_ref::<ButtonNotifyMessage>()\n                && msg.trigger_stop()\n            {\n                if msg.sender.key() == \"button-selected\" {\n                    let mut state = context.state.read_cloned_or_default::<ContextBoxProps>();\n                    state.show = !state.show;\n                    let _ = context.state.write_with(state);\n                } else if msg.sender.key() == \"button-item\" {\n                    let mut state = context.state.read_cloned_or_default::<ContextBoxProps>();\n                    state.show = !state.show;\n                    let _ = context.state.write_with(state);\n                    let params = WidgetIdMetaParams::new(msg.sender.meta());\n                    if let Some(value) = params.find_value(\"index\")\n                        && let Ok(value) = value.parse::<usize>()\n                        && let Ok(mut options) = context.props.read_cloned::<OptionsViewProps>()\n                    {\n                        options.set_index(value);\n                    }\n                }\n            }\n        }\n    });\n}\n\n#[pre_hooks(use_options_view)]\npub fn options_view(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        id,\n        idref,\n        key,\n        props,\n        state,\n        named_slots,\n        listed_slots,\n        ..\n    } = context;\n    unpack_named_slots!(named_slots => content);\n\n    let state = state.read_cloned_or_default::<ContextBoxProps>();\n    let active = props.read_cloned::<NavItemActive>().ok();\n    let options = props.read_cloned_or_default::<OptionsViewProps>();\n    let selected = listed_slots\n        .get(options.get_index())\n        .cloned()\n        .map(|mut node| {\n            node.remap_props(|props| props.with(OptionsViewMode::Selected));\n            node\n        })\n        .unwrap_or_default();\n    let content = if state.show {\n        let content = match content {\n            WidgetNode::Component(node) => {\n                WidgetNode::Component(node.listed_slots(listed_slots.into_iter().enumerate().map(\n                    |(index, mut slot)| {\n                        slot.remap_props(|props| props.with(OptionsViewMode::Option));\n                        make_widget!(button)\n                            .key(format!(\"button-item?index={index}\"))\n                            .merge_props(slot.props().cloned().unwrap_or_default())\n                            .with_props(ButtonNotifyProps(id.to_owned().into()))\n                            .named_slot(\"content\", slot)\n                    },\n                )))\n            }\n            node => node,\n        };\n        Some(\n            make_widget!(size_box)\n                .key(\"context\")\n                .merge_props(content.props().cloned().unwrap_or_default())\n                .with_props(props.read_cloned_or_default::<SizeBoxProps>())\n                .named_slot(\"content\", content),\n        )\n    } else {\n        None\n    };\n\n    make_widget!(portals_context_box)\n        .key(key)\n        .maybe_idref(idref.cloned())\n        .with_props(props.read_cloned_or_default::<PivotBoxProps>())\n        .with_props(state)\n        .named_slot(\n            \"content\",\n            make_widget!(button)\n                .key(\"button-selected\")\n                .maybe_with_props(active)\n                .with_props(ButtonNotifyProps(id.to_owned().into()))\n                .named_slot(\"content\", selected),\n        )\n        .maybe_named_slot(\"context\", content)\n        .into()\n}\n"
  },
  {
    "path": "crates/core/src/widget/component/interactive/scroll_view.rs",
    "content": "use crate::{\n    MessageData, PropsData,\n    messenger::MessageData,\n    pre_hooks,\n    widget::{\n        WidgetId, WidgetIdOrRef,\n        component::interactive::navigation::{NavJump, NavScroll, NavSignal, use_nav_scroll_view},\n        context::{WidgetContext, WidgetMountOrChangeContext},\n        utils::Vec2,\n    },\n};\nuse serde::{Deserialize, Serialize};\n\nfn is_zero(v: &Vec2) -> bool {\n    v.x.abs() < 1.0e-6 && v.y.abs() < 1.0e-6\n}\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct ScrollViewState {\n    #[serde(default)]\n    pub value: Vec2,\n    #[serde(default)]\n    pub size_factor: Vec2,\n}\n\n#[derive(PropsData, Debug, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct ScrollViewRange {\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"is_zero\")]\n    pub from: Vec2,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"is_zero\")]\n    pub to: Vec2,\n}\n\nimpl Default for ScrollViewRange {\n    fn default() -> Self {\n        Self {\n            from: Vec2 { x: 0.0, y: 0.0 },\n            to: Vec2 { x: 1.0, y: 1.0 },\n        }\n    }\n}\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct ScrollViewNotifyProps(\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"WidgetIdOrRef::is_none\")]\n    pub WidgetIdOrRef,\n);\n\n#[derive(MessageData, Debug, Clone)]\n#[message_data(crate::messenger::MessageData)]\npub struct ScrollViewNotifyMessage {\n    pub sender: WidgetId,\n    pub state: ScrollViewState,\n}\n\npub fn use_scroll_view_notified_state(context: &mut WidgetContext) {\n    context.life_cycle.change(|context| {\n        for msg in context.messenger.messages {\n            if let Some(msg) = msg.as_any().downcast_ref::<ScrollViewNotifyMessage>() {\n                let _ = context.state.write_with(msg.state.clone());\n            }\n        }\n    });\n}\n\n#[pre_hooks(use_nav_scroll_view)]\npub fn use_scroll_view(context: &mut WidgetContext) {\n    fn notify<T>(context: &WidgetMountOrChangeContext, data: T)\n    where\n        T: 'static + MessageData,\n    {\n        if let Ok(notify) = context.props.read::<ScrollViewNotifyProps>()\n            && let Some(to) = notify.0.read()\n        {\n            context.messenger.write(to, data);\n        }\n    }\n\n    context.life_cycle.mount(|context| {\n        notify(\n            &context,\n            ScrollViewNotifyMessage {\n                sender: context.id.to_owned(),\n                state: ScrollViewState::default(),\n            },\n        );\n        let _ = context.state.write_with(ScrollViewState::default());\n    });\n\n    context.life_cycle.change(|context| {\n        let mut dirty = false;\n        let mut data = context.state.read_cloned_or_default::<ScrollViewState>();\n        let range = context.props.read::<ScrollViewRange>();\n        for msg in context.messenger.messages {\n            if let Some(NavSignal::Jump(NavJump::Scroll(NavScroll::Change(\n                value,\n                factor,\n                relative,\n            )))) = msg.as_any().downcast_ref()\n            {\n                if *relative {\n                    data.value.x += value.x;\n                    data.value.y += value.y;\n                } else {\n                    data.value = *value;\n                }\n                if factor.x <= 1.0 {\n                    data.value.x = 0.0;\n                }\n                if factor.y <= 1.0 {\n                    data.value.y = 0.0;\n                }\n                if let Ok(range) = &range {\n                    data.value.x = data.value.x.max(range.from.x).min(range.to.x);\n                    data.value.y = data.value.y.max(range.from.y).min(range.to.y);\n                }\n                data.size_factor = *factor;\n                dirty = true;\n            }\n        }\n        if dirty {\n            notify(\n                &context,\n                ScrollViewNotifyMessage {\n                    sender: context.id.to_owned(),\n                    state: data.clone(),\n                },\n            );\n            let _ = context.state.write_with(data);\n        }\n    });\n}\n"
  },
  {
    "path": "crates/core/src/widget/component/interactive/slider_view.rs",
    "content": "use crate::{\n    PropsData, Scalar, pre_hooks, unpack_named_slots,\n    view_model::ViewModelValue,\n    widget::{\n        component::interactive::{\n            button::{ButtonNotifyMessage, ButtonNotifyProps, ButtonProps, use_button},\n            navigation::{\n                NavSignal, NavTrackingNotifyMessage, NavTrackingNotifyProps, use_nav_item,\n                use_nav_tracking_self,\n            },\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::area::AreaBoxNode,\n    },\n};\nuse intuicio_data::managed::ManagedLazy;\nuse serde::{Deserialize, Serialize};\nuse std::ops::{Deref, DerefMut};\n\nfn is_zero(value: &Scalar) -> bool {\n    value.abs() < 1.0e-6\n}\n\npub trait SliderViewProxy: Send + Sync {\n    fn get(&self) -> Scalar;\n    fn set(&mut self, value: Scalar);\n}\n\nmacro_rules! impl_proxy {\n    ($type:ty) => {\n        impl SliderViewProxy for $type {\n            fn get(&self) -> Scalar {\n                *self as _\n            }\n\n            fn set(&mut self, value: Scalar) {\n                *self = value as _;\n            }\n        }\n    };\n    (@round $type:ty) => {\n        impl SliderViewProxy for $type {\n            fn get(&self) -> Scalar {\n                *self as _\n            }\n\n            fn set(&mut self, value: Scalar) {\n                *self = value.round() as _;\n            }\n        }\n    };\n}\n\nimpl_proxy!(@round u8);\nimpl_proxy!(@round u16);\nimpl_proxy!(@round u32);\nimpl_proxy!(@round u64);\nimpl_proxy!(@round u128);\nimpl_proxy!(@round usize);\nimpl_proxy!(@round i8);\nimpl_proxy!(@round i16);\nimpl_proxy!(@round i32);\nimpl_proxy!(@round i64);\nimpl_proxy!(@round i128);\nimpl_proxy!(@round isize);\nimpl_proxy!(f32);\nimpl_proxy!(f64);\n\nimpl<T> SliderViewProxy for ViewModelValue<T>\nwhere\n    T: SliderViewProxy,\n{\n    fn get(&self) -> Scalar {\n        self.deref().get()\n    }\n\n    fn set(&mut self, value: Scalar) {\n        self.deref_mut().set(value);\n    }\n}\n\n#[derive(Clone)]\npub struct SliderInput(ManagedLazy<dyn SliderViewProxy>);\n\nimpl SliderInput {\n    pub fn new(data: ManagedLazy<impl SliderViewProxy + 'static>) -> Self {\n        let (lifetime, data) = data.into_inner();\n        let data = data as *mut dyn SliderViewProxy;\n        unsafe { Self(ManagedLazy::<dyn SliderViewProxy>::new_raw(data, lifetime).unwrap()) }\n    }\n\n    pub fn into_inner(self) -> ManagedLazy<dyn SliderViewProxy> {\n        self.0\n    }\n\n    pub fn get<T: TryFrom<Scalar> + Default>(&self) -> T {\n        self.0\n            .read()\n            .map(|data| data.get())\n            .and_then(|value| T::try_from(value).ok())\n            .unwrap_or_default()\n    }\n\n    pub fn set<T: TryInto<Scalar>>(&mut self, value: T) {\n        if let Some(mut data) = self.0.write()\n            && let Ok(value) = value.try_into()\n        {\n            data.set(value);\n        }\n    }\n}\n\nimpl std::fmt::Debug for SliderInput {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_tuple(\"SliderInput\")\n            .field(&self.0.read().map(|data| data.get()).unwrap_or_default())\n            .finish()\n    }\n}\n\nimpl<T: SliderViewProxy + 'static> From<ManagedLazy<T>> for SliderInput {\n    fn from(value: ManagedLazy<T>) -> Self {\n        Self::new(value)\n    }\n}\n\n#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]\npub enum SliderViewDirection {\n    #[default]\n    LeftToRight,\n    RightToLeft,\n    TopToBottom,\n    BottomToTop,\n}\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct SliderViewProps {\n    #[serde(default)]\n    #[serde(skip)]\n    pub input: Option<SliderInput>,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"is_zero\")]\n    pub from: Scalar,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"is_zero\")]\n    pub to: Scalar,\n    #[serde(default)]\n    pub direction: SliderViewDirection,\n}\n\nimpl SliderViewProps {\n    pub fn get_value(&self) -> Scalar {\n        self.input\n            .as_ref()\n            .map(|input| input.get::<Scalar>())\n            .unwrap_or_default()\n    }\n\n    pub fn set_value(&mut self, value: Scalar) {\n        if let Some(input) = self.input.as_mut() {\n            input.set(value);\n        }\n    }\n\n    pub fn get_percentage(&self) -> Scalar {\n        (self.get_value() - self.from) / (self.to - self.from)\n    }\n\n    pub fn set_percentage(&mut self, value: Scalar) {\n        self.set_value(value * (self.to - self.from) + self.from)\n    }\n}\n\n#[pre_hooks(use_button, use_nav_tracking_self)]\npub fn use_slider_view(context: &mut WidgetContext) {\n    context\n        .props\n        .write(ButtonNotifyProps(context.id.to_owned().into()));\n    context\n        .props\n        .write(NavTrackingNotifyProps(context.id.to_owned().into()));\n\n    context.life_cycle.unmount(|context| {\n        context.signals.write(NavSignal::Unlock);\n    });\n\n    context.life_cycle.change(|context| {\n        for msg in context.messenger.messages {\n            if let Some(msg) = msg.as_any().downcast_ref::<ButtonNotifyMessage>() {\n                if msg.trigger_start() {\n                    context.signals.write(NavSignal::Lock);\n                }\n                if msg.trigger_stop() {\n                    context.signals.write(NavSignal::Unlock);\n                }\n            } else if let Some(msg) = msg.as_any().downcast_ref::<NavTrackingNotifyMessage>() {\n                let button = context.state.read_cloned_or_default::<ButtonProps>();\n                if button.selected && button.trigger {\n                    let mut props = context.props.read_cloned_or_default::<SliderViewProps>();\n                    let value = match props.direction {\n                        SliderViewDirection::LeftToRight => msg.state.factor.x,\n                        SliderViewDirection::RightToLeft => 1.0 - msg.state.factor.x,\n                        SliderViewDirection::TopToBottom => msg.state.factor.y,\n                        SliderViewDirection::BottomToTop => 1.0 - msg.state.factor.y,\n                    }\n                    .clamp(0.0, 1.0);\n                    let value = value * (props.to - props.from) + props.from;\n                    if let Some(input) = props.input.as_mut() {\n                        input.set(value);\n                    }\n                }\n            }\n        }\n    });\n}\n\n#[pre_hooks(use_nav_item, use_slider_view)]\npub fn slider_view(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        id,\n        props,\n        state,\n        named_slots,\n        ..\n    } = context;\n    unpack_named_slots!(named_slots => content);\n\n    if let Some(p) = content.props_mut() {\n        p.write(state.read_cloned_or_default::<ButtonProps>());\n        p.write(props.read_cloned_or_default::<SliderViewProps>());\n    }\n\n    AreaBoxNode {\n        id: id.to_owned(),\n        slot: Box::new(content),\n    }\n    .into()\n}\n"
  },
  {
    "path": "crates/core/src/widget/component/mod.rs",
    "content": "pub mod containers;\npub mod image_box;\npub mod interactive;\npub mod space_box;\npub mod text_box;\n\nuse crate::{\n    MessageData, PrefabValue, PropsData, Scalar,\n    messenger::Message,\n    props::{Props, PropsData},\n    widget::{\n        FnWidget, WidgetId, WidgetIdOrRef, WidgetRef,\n        context::WidgetContext,\n        node::{WidgetNode, WidgetNodePrefab},\n        utils::{Rect, Vec2},\n    },\n};\nuse intuicio_data::type_hash::TypeHash;\nuse serde::{Deserialize, Serialize};\nuse std::{collections::HashMap, convert::TryFrom};\n\nfn is_false(v: &bool) -> bool {\n    !*v\n}\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct MessageForwardProps {\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"WidgetIdOrRef::is_none\")]\n    pub to: WidgetIdOrRef,\n    #[serde(default)]\n    #[serde(skip)]\n    pub types: Vec<TypeHash>,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"is_false\")]\n    pub no_wrap: bool,\n}\n\nimpl MessageForwardProps {\n    pub fn with_type<T>(mut self) -> Self {\n        self.types.push(TypeHash::of::<T>());\n        self\n    }\n}\n\n#[derive(MessageData, Debug, Clone)]\n#[message_data(crate::messenger::MessageData)]\npub struct ForwardedMessage {\n    pub sender: WidgetId,\n    pub data: Message,\n}\n\npub fn use_message_forward(context: &mut WidgetContext) {\n    context.life_cycle.change(|context| {\n        let (id, no_wrap, types) = match context.props.read::<MessageForwardProps>() {\n            Ok(forward) => match forward.to.read() {\n                Some(id) => (id, forward.no_wrap, &forward.types),\n                _ => return,\n            },\n            _ => match context.shared_props.read::<MessageForwardProps>() {\n                Ok(forward) => match forward.to.read() {\n                    Some(id) => (id, forward.no_wrap, &forward.types),\n                    _ => return,\n                },\n                _ => return,\n            },\n        };\n        for msg in context.messenger.messages {\n            let t = msg.type_hash();\n            if types.contains(&t) {\n                if no_wrap {\n                    context\n                        .messenger\n                        .write_raw(id.to_owned(), msg.clone_message());\n                } else {\n                    context.messenger.write(\n                        id.to_owned(),\n                        ForwardedMessage {\n                            sender: context.id.to_owned(),\n                            data: msg.clone_message(),\n                        },\n                    );\n                }\n            }\n        }\n    });\n}\n\n#[derive(MessageData, Debug, Copy, Clone, PartialEq)]\n#[message_data(crate::messenger::MessageData)]\npub enum ResizeListenerSignal {\n    Register,\n    Unregister,\n    Change(Vec2),\n}\n\npub fn use_resize_listener(context: &mut WidgetContext) {\n    context.life_cycle.mount(|context| {\n        context.signals.write(ResizeListenerSignal::Register);\n    });\n\n    context.life_cycle.unmount(|context| {\n        context.signals.write(ResizeListenerSignal::Unregister);\n    });\n}\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct RelativeLayoutProps {\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"WidgetIdOrRef::is_none\")]\n    pub relative_to: WidgetIdOrRef,\n}\n\n#[derive(MessageData, Debug, Clone, PartialEq)]\n#[message_data(crate::messenger::MessageData)]\npub enum RelativeLayoutListenerSignal {\n    /// (relative to id)\n    Register(WidgetId),\n    Unregister,\n    /// (outer box size, inner box rect)\n    Change(Vec2, Rect),\n}\n\npub fn use_relative_layout_listener(context: &mut WidgetContext) {\n    context.life_cycle.mount(|context| {\n        if let Ok(props) = context.props.read::<RelativeLayoutProps>()\n            && let Some(relative_to) = props.relative_to.read()\n        {\n            context\n                .signals\n                .write(RelativeLayoutListenerSignal::Register(relative_to));\n        }\n    });\n\n    // TODO: when user will change widget IDs after mounting, we might want to re-register\n    // this widget with new IDs.\n\n    context.life_cycle.unmount(|context| {\n        context\n            .signals\n            .write(RelativeLayoutListenerSignal::Unregister);\n    });\n}\n\n#[derive(PropsData, Debug, Copy, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct WidgetAlpha(pub Scalar);\n\nimpl Default for WidgetAlpha {\n    fn default() -> Self {\n        Self(1.0)\n    }\n}\n\nimpl WidgetAlpha {\n    pub fn multiply(&mut self, alpha: Scalar) {\n        self.0 *= alpha;\n    }\n}\n\n#[derive(Clone)]\npub struct WidgetComponent {\n    pub processor: FnWidget,\n    pub type_name: String,\n    pub key: Option<String>,\n    pub idref: Option<WidgetRef>,\n    pub props: Props,\n    pub shared_props: Option<Props>,\n    pub listed_slots: Vec<WidgetNode>,\n    pub named_slots: HashMap<String, WidgetNode>,\n}\n\nimpl WidgetComponent {\n    pub fn new(processor: FnWidget, type_name: impl ToString) -> Self {\n        Self {\n            processor,\n            type_name: type_name.to_string(),\n            key: None,\n            idref: None,\n            props: Props::default(),\n            shared_props: None,\n            listed_slots: Vec::new(),\n            named_slots: HashMap::new(),\n        }\n    }\n\n    pub fn key<T>(mut self, v: T) -> Self\n    where\n        T: ToString,\n    {\n        self.key = Some(v.to_string());\n        self\n    }\n\n    pub fn idref<T>(mut self, v: T) -> Self\n    where\n        T: Into<WidgetRef>,\n    {\n        self.idref = Some(v.into());\n        self\n    }\n\n    pub fn maybe_idref<T>(mut self, v: Option<T>) -> Self\n    where\n        T: Into<WidgetRef>,\n    {\n        self.idref = v.map(|v| v.into());\n        self\n    }\n\n    pub fn with_props<T>(mut self, v: T) -> Self\n    where\n        T: 'static + PropsData,\n    {\n        self.props.write(v);\n        self\n    }\n\n    pub fn maybe_with_props<T>(self, v: Option<T>) -> Self\n    where\n        T: 'static + PropsData,\n    {\n        if let Some(v) = v {\n            self.with_props(v)\n        } else {\n            self\n        }\n    }\n\n    pub fn merge_props(mut self, v: Props) -> Self {\n        let props = std::mem::take(&mut self.props);\n        self.props = props.merge(v);\n        self\n    }\n\n    pub fn with_shared_props<T>(mut self, v: T) -> Self\n    where\n        T: 'static + PropsData,\n    {\n        if let Some(props) = &mut self.shared_props {\n            props.write(v);\n        } else {\n            self.shared_props = Some(Props::new(v));\n        }\n        self\n    }\n\n    pub fn maybe_with_shared_props<T>(self, v: Option<T>) -> Self\n    where\n        T: 'static + PropsData,\n    {\n        if let Some(v) = v {\n            self.with_shared_props(v)\n        } else {\n            self\n        }\n    }\n\n    pub fn merge_shared_props(mut self, v: Props) -> Self {\n        if let Some(props) = self.shared_props.take() {\n            self.shared_props = Some(props.merge(v));\n        } else {\n            self.shared_props = Some(v);\n        }\n        self\n    }\n\n    pub fn listed_slot<T>(mut self, v: T) -> Self\n    where\n        T: Into<WidgetNode>,\n    {\n        self.listed_slots.push(v.into());\n        self\n    }\n\n    pub fn maybe_listed_slot<T>(mut self, v: Option<T>) -> Self\n    where\n        T: Into<WidgetNode>,\n    {\n        if let Some(v) = v {\n            self.listed_slots.push(v.into());\n        }\n        self\n    }\n\n    pub fn listed_slots<I, T>(mut self, v: I) -> Self\n    where\n        I: IntoIterator<Item = T>,\n        T: Into<WidgetNode>,\n    {\n        self.listed_slots.extend(v.into_iter().map(|v| v.into()));\n        self\n    }\n\n    pub fn named_slot<T>(mut self, k: impl ToString, v: T) -> Self\n    where\n        T: Into<WidgetNode>,\n    {\n        self.named_slots.insert(k.to_string(), v.into());\n        self\n    }\n\n    pub fn maybe_named_slot<T>(mut self, k: impl ToString, v: Option<T>) -> Self\n    where\n        T: Into<WidgetNode>,\n    {\n        if let Some(v) = v {\n            self.named_slots.insert(k.to_string(), v.into());\n        }\n        self\n    }\n\n    pub fn named_slots<I, K, T>(mut self, v: I) -> Self\n    where\n        I: IntoIterator<Item = (K, T)>,\n        K: ToString,\n        T: Into<WidgetNode>,\n    {\n        self.named_slots\n            .extend(v.into_iter().map(|(k, v)| (k.to_string(), v.into())));\n        self\n    }\n\n    pub fn remap_props<F>(&mut self, mut f: F)\n    where\n        F: FnMut(Props) -> Props,\n    {\n        let props = std::mem::take(&mut self.props);\n        self.props = (f)(props);\n    }\n\n    pub fn remap_shared_props<F>(&mut self, mut f: F)\n    where\n        F: FnMut(Props) -> Props,\n    {\n        if let Some(shared_props) = &mut self.shared_props {\n            let props = std::mem::take(shared_props);\n            *shared_props = (f)(props);\n        } else {\n            self.shared_props = Some((f)(Default::default()));\n        }\n    }\n}\n\nimpl std::fmt::Debug for WidgetComponent {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let mut s = f.debug_struct(\"WidgetComponent\");\n        s.field(\"type_name\", &self.type_name);\n        if let Some(key) = &self.key {\n            s.field(\"key\", key);\n        }\n        s.field(\"props\", &self.props);\n        s.field(\"shared_props\", &self.shared_props);\n        if !self.listed_slots.is_empty() {\n            s.field(\"listed_slots\", &self.listed_slots);\n        }\n        if !self.named_slots.is_empty() {\n            s.field(\"named_slots\", &self.named_slots);\n        }\n        s.finish()\n    }\n}\n\nimpl TryFrom<WidgetNode> for WidgetComponent {\n    type Error = ();\n\n    fn try_from(node: WidgetNode) -> Result<Self, Self::Error> {\n        if let WidgetNode::Component(v) = node {\n            Ok(v)\n        } else {\n            Err(())\n        }\n    }\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub(crate) struct WidgetComponentPrefab {\n    #[serde(default)]\n    pub type_name: String,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub key: Option<String>,\n    #[serde(default)]\n    pub props: PrefabValue,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub shared_props: Option<PrefabValue>,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"Vec::is_empty\")]\n    pub listed_slots: Vec<WidgetNodePrefab>,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"HashMap::is_empty\")]\n    pub named_slots: HashMap<String, WidgetNodePrefab>,\n}\n"
  },
  {
    "path": "crates/core/src/widget/component/space_box.rs",
    "content": "use crate::{\n    PropsData, Scalar,\n    widget::{\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::size::{SizeBoxNode, SizeBoxSizeValue},\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct SpaceBoxProps {\n    #[serde(default)]\n    pub width: Scalar,\n    #[serde(default)]\n    pub height: Scalar,\n}\n\nimpl SpaceBoxProps {\n    pub fn cube(value: Scalar) -> Self {\n        Self {\n            width: value,\n            height: value,\n        }\n    }\n\n    pub fn horizontal(width: Scalar) -> Self {\n        Self { width, height: 0.0 }\n    }\n\n    pub fn vertical(height: Scalar) -> Self {\n        Self { width: 0.0, height }\n    }\n}\n\npub fn space_box(context: WidgetContext) -> WidgetNode {\n    let WidgetContext { id, props, .. } = context;\n\n    let SpaceBoxProps { width, height } = props.read_cloned_or_default();\n\n    SizeBoxNode {\n        id: id.to_owned(),\n        props: props.clone(),\n        width: SizeBoxSizeValue::Exact(width),\n        height: SizeBoxSizeValue::Exact(height),\n        ..Default::default()\n    }\n    .into()\n}\n"
  },
  {
    "path": "crates/core/src/widget/component/text_box.rs",
    "content": "use crate::{\n    PropsData,\n    widget::{\n        component::WidgetAlpha,\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::text::{\n            TextBoxDirection, TextBoxFont, TextBoxHorizontalAlign, TextBoxNode, TextBoxSizeValue,\n            TextBoxVerticalAlign,\n        },\n        utils::{Color, Transform},\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct TextBoxProps {\n    #[serde(default)]\n    pub text: String,\n    #[serde(default)]\n    pub width: TextBoxSizeValue,\n    #[serde(default)]\n    pub height: TextBoxSizeValue,\n    #[serde(default)]\n    pub horizontal_align: TextBoxHorizontalAlign,\n    #[serde(default)]\n    pub vertical_align: TextBoxVerticalAlign,\n    #[serde(default)]\n    pub direction: TextBoxDirection,\n    #[serde(default)]\n    pub font: TextBoxFont,\n    #[serde(default)]\n    pub color: Color,\n    #[serde(default)]\n    pub transform: Transform,\n}\n\npub fn text_box(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        id,\n        props,\n        shared_props,\n        ..\n    } = context;\n\n    let TextBoxProps {\n        width,\n        height,\n        text,\n        horizontal_align,\n        vertical_align,\n        direction,\n        font,\n        mut color,\n        transform,\n    } = props.read_cloned_or_default();\n\n    let alpha = shared_props.read_cloned_or_default::<WidgetAlpha>().0;\n    color.a *= alpha;\n\n    TextBoxNode {\n        id: id.to_owned(),\n        props: props.clone(),\n        text,\n        width,\n        height,\n        horizontal_align,\n        vertical_align,\n        direction,\n        font,\n        color,\n        transform,\n    }\n    .into()\n}\n"
  },
  {
    "path": "crates/core/src/widget/context.rs",
    "content": "use crate::{\n    animator::{Animator, AnimatorStates},\n    messenger::{MessageSender, Messenger},\n    props::Props,\n    signals::SignalSender,\n    state::State,\n    view_model::ViewModelCollectionView,\n    widget::{WidgetId, WidgetLifeCycle, WidgetRef, node::WidgetNode},\n};\nuse std::collections::HashMap;\n\npub struct WidgetContext<'a> {\n    pub id: &'a WidgetId,\n    pub idref: Option<&'a WidgetRef>,\n    pub key: &'a str,\n    pub props: &'a mut Props,\n    pub shared_props: &'a mut Props,\n    pub state: State<'a>,\n    pub animator: &'a AnimatorStates,\n    pub life_cycle: &'a mut WidgetLifeCycle,\n    pub named_slots: HashMap<String, WidgetNode>,\n    pub listed_slots: Vec<WidgetNode>,\n    pub view_models: ViewModelCollectionView<'a>,\n}\n\nimpl WidgetContext<'_> {\n    pub fn take_named_slots(&mut self) -> HashMap<String, WidgetNode> {\n        std::mem::take(&mut self.named_slots)\n    }\n\n    pub fn take_named_slot(&mut self, name: &str) -> WidgetNode {\n        self.named_slots.remove(name).unwrap_or_default()\n    }\n\n    pub fn take_listed_slots(&mut self) -> Vec<WidgetNode> {\n        std::mem::take(&mut self.listed_slots)\n    }\n\n    pub fn use_hook<F>(&mut self, mut f: F) -> &mut Self\n    where\n        F: FnMut(&mut Self),\n    {\n        (f)(self);\n        self\n    }\n}\n\nimpl std::fmt::Debug for WidgetContext<'_> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"WidgetContext\")\n            .field(\"id\", &self.id)\n            .field(\"key\", &self.key)\n            .field(\"props\", &self.props)\n            .field(\"shared_props\", &self.shared_props)\n            .field(\"named_slots\", &self.named_slots)\n            .field(\"listed_slots\", &self.listed_slots)\n            .finish()\n    }\n}\n\npub struct WidgetMountOrChangeContext<'a> {\n    pub id: &'a WidgetId,\n    pub props: &'a Props,\n    pub shared_props: &'a Props,\n    pub state: State<'a>,\n    pub messenger: Messenger<'a>,\n    pub signals: SignalSender,\n    pub animator: Animator<'a>,\n    pub view_models: ViewModelCollectionView<'a>,\n}\n\npub struct WidgetUnmountContext<'a> {\n    pub id: &'a WidgetId,\n    pub state: &'a Props,\n    pub messenger: &'a MessageSender,\n    pub signals: SignalSender,\n    pub view_models: ViewModelCollectionView<'a>,\n}\n"
  },
  {
    "path": "crates/core/src/widget/mod.rs",
    "content": "//! Widget types and the core component collection\n\npub mod component;\npub mod context;\npub mod node;\npub mod unit;\npub mod utils;\n\nuse crate::{\n    Prefab, PropsData,\n    application::Application,\n    props::PropsData,\n    widget::{\n        context::{WidgetContext, WidgetMountOrChangeContext, WidgetUnmountContext},\n        node::WidgetNode,\n    },\n};\nuse serde::{Deserialize, Serialize};\nuse std::{\n    borrow::Cow,\n    collections::hash_map::DefaultHasher,\n    convert::TryFrom,\n    hash::{Hash, Hasher},\n    ops::{Deref, Range},\n    str::FromStr,\n    sync::{Arc, RwLock},\n};\n\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct WidgetIdDef(pub String);\n\nimpl From<WidgetId> for WidgetIdDef {\n    fn from(data: WidgetId) -> Self {\n        Self(data.to_string())\n    }\n}\n\n#[derive(Debug, Clone, Copy)]\npub struct WidgetIdMetaParam<'a> {\n    pub name: &'a str,\n    pub value: Option<&'a str>,\n}\n\nimpl WidgetIdMetaParam<'_> {\n    pub fn is_flag(&self) -> bool {\n        self.value.is_none()\n    }\n\n    pub fn has_value(&self) -> bool {\n        self.value.is_some()\n    }\n}\n\n#[derive(Debug, Clone, Copy)]\npub struct WidgetIdMetaParams<'a>(&'a str);\n\nimpl<'a> WidgetIdMetaParams<'a> {\n    pub fn new(meta: &'a str) -> Self {\n        Self(meta)\n    }\n\n    pub fn iter(&'_ self) -> impl Iterator<Item = WidgetIdMetaParam<'_>> {\n        self.0.split('&').filter_map(|part| {\n            if let Some(index) = part.find('=') {\n                let name = &part[0..index];\n                let value = &part[(index + b\"=\".len())..];\n                if name.is_empty() {\n                    None\n                } else {\n                    Some(WidgetIdMetaParam {\n                        name,\n                        value: Some(value),\n                    })\n                }\n            } else if part.is_empty() {\n                None\n            } else {\n                Some(WidgetIdMetaParam {\n                    name: part,\n                    value: None,\n                })\n            }\n        })\n    }\n\n    pub fn find(&'_ self, name: &str) -> Option<WidgetIdMetaParam<'_>> {\n        self.iter().find(|param| param.name == name)\n    }\n\n    pub fn has_flag(&self, name: &str) -> bool {\n        self.iter()\n            .any(|param| param.name == name && param.is_flag())\n    }\n\n    pub fn find_value(&self, name: &str) -> Option<&str> {\n        self.iter().find_map(|param| {\n            if param.name == name {\n                param.value\n            } else {\n                None\n            }\n        })\n    }\n}\n\n#[derive(PropsData, Default, Clone, Serialize, Deserialize)]\n#[serde(try_from = \"WidgetIdDef\")]\n#[serde(into = \"WidgetIdDef\")]\npub struct WidgetId {\n    id: String,\n    type_name: Range<usize>,\n    /// [(key range, meta range)]\n    parts: Vec<(Range<usize>, Range<usize>)>,\n}\n\nimpl WidgetId {\n    pub fn empty() -> Self {\n        Self {\n            id: \":\".to_owned(),\n            type_name: 0..0,\n            parts: Default::default(),\n        }\n    }\n\n    pub fn new(type_name: &str, path: &[Cow<'_, str>]) -> Self {\n        if path.is_empty() {\n            return Self {\n                id: format!(\"{type_name}:\"),\n                type_name: 0..type_name.len(),\n                parts: Default::default(),\n            };\n        }\n        let count = type_name.len()\n            + b\":\".len()\n            + path.iter().map(|part| part.len()).sum::<usize>()\n            + path.len().saturating_sub(1) * b\"/\".len();\n        let mut result = String::with_capacity(count);\n        let mut position = result.len();\n        result.push_str(type_name);\n        let type_name = 0..result.len();\n        result.push(':');\n        let parts = path\n            .iter()\n            .enumerate()\n            .map(|(index, part)| {\n                if index > 0 {\n                    result.push('/');\n                }\n                position = result.len();\n                result.push_str(part);\n                let range = position..result.len();\n                if let Some(index) = part.find('?') {\n                    let key = range.start..(range.start + index);\n                    let meta = (range.start + index + b\"?\".len())..range.end;\n                    (key, meta)\n                } else {\n                    let meta = range.end..range.end;\n                    (range, meta)\n                }\n            })\n            .collect::<Vec<_>>();\n        Self {\n            id: result,\n            type_name,\n            parts,\n        }\n    }\n\n    pub fn push(&self, part: &str) -> Self {\n        let count = self.id.len() + b\"/\".len();\n        let mut result = String::with_capacity(count);\n        result.push_str(&self.id);\n        if self.depth() > 0 {\n            result.push('/');\n        }\n        let position = result.len();\n        result.push_str(part);\n        let range = position..result.len();\n        let (key, meta) = if let Some(index) = part.find('?') {\n            let key = range.start..(range.start + index);\n            let meta = (range.start + index + b\"?\".len())..range.end;\n            (key, meta)\n        } else {\n            let meta = range.end..range.end;\n            (range, meta)\n        };\n        let parts = self\n            .parts\n            .iter()\n            .cloned()\n            .chain(std::iter::once((key, meta)))\n            .collect();\n        Self {\n            id: result,\n            type_name: self.type_name.to_owned(),\n            parts,\n        }\n    }\n\n    pub fn pop(&self) -> Self {\n        let parts = self.parts[0..(self.parts.len().saturating_sub(1))].to_owned();\n        let result = if let Some(range) = parts.last() {\n            self.id[0..range.1.end].to_owned()\n        } else {\n            format!(\"{}:\", self.type_name())\n        };\n        Self {\n            id: result,\n            type_name: self.type_name.to_owned(),\n            parts,\n        }\n    }\n\n    #[inline]\n    pub fn is_valid(&self) -> bool {\n        !self.id.is_empty()\n    }\n\n    #[inline]\n    pub fn depth(&self) -> usize {\n        self.parts.len()\n    }\n\n    #[inline]\n    pub fn type_name(&self) -> &str {\n        &self.id.as_str()[self.type_name.clone()]\n    }\n\n    #[inline]\n    pub fn path(&self) -> &str {\n        if self.parts.is_empty() {\n            &self.id.as_str()[0..0]\n        } else {\n            &self.id.as_str()[self.parts.first().unwrap().0.start..self.parts.last().unwrap().1.end]\n        }\n    }\n\n    #[inline]\n    pub fn key(&self) -> &str {\n        if self.parts.is_empty() {\n            &self.id.as_str()[0..0]\n        } else {\n            &self.id[self.parts.last().cloned().unwrap().0]\n        }\n    }\n\n    #[inline]\n    pub fn meta(&self) -> &str {\n        if self.parts.is_empty() {\n            &self.id.as_str()[0..0]\n        } else {\n            &self.id[self.parts.last().cloned().unwrap().1]\n        }\n    }\n\n    #[inline]\n    pub fn part(&self, index: usize) -> Option<&str> {\n        self.parts\n            .get(index)\n            .cloned()\n            .map(|(key, meta)| &self.id[key.start..meta.end])\n    }\n\n    #[inline]\n    pub fn part_key_meta(&self, index: usize) -> Option<(&str, &str)> {\n        self.parts\n            .get(index)\n            .cloned()\n            .map(|(key, meta)| (&self.id[key], &self.id[meta]))\n    }\n\n    pub fn range(&self, from_inclusive: usize, to_exclusive: usize) -> &str {\n        if self.parts.is_empty() {\n            return &self.id[0..0];\n        }\n        let start = from_inclusive.min(self.parts.len().saturating_sub(1));\n        let end = to_exclusive\n            .saturating_sub(1)\n            .max(start)\n            .min(self.parts.len().saturating_sub(1));\n        let start = self.parts[start].0.start;\n        let end = self.parts[end].1.end;\n        &self.id[start..end]\n    }\n\n    pub fn is_subset_of(&self, other: &Self) -> bool {\n        match self.distance_to(other) {\n            Ok(v) => v < 0,\n            _ => false,\n        }\n    }\n\n    pub fn is_superset_of(&self, other: &Self) -> bool {\n        match self.distance_to(other) {\n            Ok(v) => v > 0,\n            _ => false,\n        }\n    }\n\n    pub fn distance_to(&self, other: &Self) -> Result<isize, isize> {\n        for index in 0..self.depth().max(other.depth()) {\n            match (self.part(index), other.part(index)) {\n                (None, None) => return Ok(0),\n                (None, Some(_)) | (Some(_), None) => {\n                    return Ok(self.depth() as isize - other.depth() as isize);\n                }\n                (Some(a), Some(b)) => {\n                    if a != b {\n                        return Err(index as isize - other.depth() as isize);\n                    }\n                }\n            }\n        }\n        Ok(0)\n    }\n\n    #[inline]\n    pub fn parts(&self) -> impl Iterator<Item = &str> + '_ {\n        self.parts\n            .iter()\n            .map(move |(key, meta)| &self.id[key.start..meta.end])\n    }\n\n    #[inline]\n    pub fn parts_key_meta(&self) -> impl Iterator<Item = (&str, &str)> + '_ {\n        self.parts\n            .iter()\n            .cloned()\n            .map(move |(key, meta)| (&self.id[key], &self.id[meta]))\n    }\n\n    #[inline]\n    pub fn rparts(&self) -> impl Iterator<Item = &str> + '_ {\n        self.parts\n            .iter()\n            .rev()\n            .map(move |(key, meta)| &self.id[key.start..meta.end])\n    }\n\n    #[inline]\n    pub fn rparts_key_meta(&self) -> impl Iterator<Item = (&str, &str)> + '_ {\n        self.parts\n            .iter()\n            .rev()\n            .cloned()\n            .map(move |(key, meta)| (&self.id[key], &self.id[meta]))\n    }\n\n    pub fn hashed_value(&self) -> u64 {\n        let mut hasher = DefaultHasher::new();\n        self.hash(&mut hasher);\n        hasher.finish()\n    }\n}\n\nimpl Hash for WidgetId {\n    fn hash<H: Hasher>(&self, state: &mut H) {\n        self.id.hash(state);\n    }\n}\n\nimpl PartialEq for WidgetId {\n    fn eq(&self, other: &Self) -> bool {\n        self.id == other.id\n    }\n}\n\nimpl Eq for WidgetId {}\n\nimpl Deref for WidgetId {\n    type Target = str;\n\n    fn deref(&self) -> &Self::Target {\n        &self.id\n    }\n}\n\nimpl AsRef<str> for WidgetId {\n    fn as_ref(&self) -> &str {\n        &self.id\n    }\n}\n\nimpl FromStr for WidgetId {\n    type Err = ();\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        if let Some(index) = s.find(':') {\n            let type_name = s[..index].to_owned();\n            let rest = &s[(index + b\":\".len())..];\n            let path = rest.split('/').map(Cow::Borrowed).collect::<Vec<_>>();\n            Ok(Self::new(&type_name, &path))\n        } else {\n            Err(())\n        }\n    }\n}\n\nimpl std::fmt::Debug for WidgetId {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"WidgetId\")\n            .field(\"id\", &self.id)\n            .field(\"type_name\", &&self.id[self.type_name.clone()])\n            .field(\n                \"parts\",\n                &self\n                    .parts\n                    .iter()\n                    .map(|(key, meta)| (&self.id[key.to_owned()], &self.id[meta.to_owned()]))\n                    .collect::<Vec<_>>(),\n            )\n            .finish()\n    }\n}\n\nimpl std::fmt::Display for WidgetId {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.write_str(self.as_ref())\n    }\n}\n\nimpl TryFrom<WidgetIdDef> for WidgetId {\n    type Error = String;\n\n    fn try_from(id: WidgetIdDef) -> Result<Self, Self::Error> {\n        match Self::from_str(&id.0) {\n            Ok(id) => Ok(id),\n            Err(_) => Err(format!(\"Could not parse id: `{}`\", id.0)),\n        }\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct WidgetIdCommon {\n    id: Option<WidgetId>,\n    count: usize,\n}\n\nimpl Default for WidgetIdCommon {\n    fn default() -> Self {\n        Self {\n            id: None,\n            count: usize::MAX,\n        }\n    }\n}\n\nimpl WidgetIdCommon {\n    pub fn new(id: WidgetId) -> Self {\n        Self {\n            count: id.depth(),\n            id: Some(id),\n        }\n    }\n\n    pub fn include(&mut self, id: &WidgetId) -> &mut Self {\n        if self.id.is_none() {\n            self.id = Some(id.to_owned());\n            self.count = id.depth();\n            return self;\n        }\n        if let Some(source) = self.id.as_ref() {\n            for index in 0..self.count.min(id.depth()) {\n                if source.part(index) != id.part(index) {\n                    self.count = index;\n                    return self;\n                }\n            }\n        }\n        self\n    }\n\n    pub fn include_other(&mut self, other: &Self) -> &mut Self {\n        if let Some(id) = other.id.as_ref() {\n            if self.id.is_none() {\n                self.id = Some(id.to_owned());\n                self.count = other.count;\n                return self;\n            }\n            if let Some(source) = self.id.as_ref() {\n                for index in 0..self.count.min(other.count) {\n                    if source.part(index) != id.part(index) {\n                        self.count = index;\n                        return self;\n                    }\n                }\n            }\n        }\n        self\n    }\n\n    pub fn is_valid(&self) -> bool {\n        self.id.is_some()\n    }\n\n    pub fn parts(&self) -> Option<impl Iterator<Item = &str>> {\n        self.id\n            .as_ref()\n            .map(|id| (0..self.count).map_while(move |index| id.part(index)))\n    }\n\n    pub fn path(&self) -> Option<&str> {\n        self.id\n            .as_ref()\n            .map(|id| id.range(0, self.count))\n            .filter(|id| !id.is_empty())\n    }\n}\n\nimpl<'a> FromIterator<&'a WidgetId> for WidgetIdCommon {\n    fn from_iter<T: IntoIterator<Item = &'a WidgetId>>(iter: T) -> Self {\n        let mut result = Self::default();\n        for id in iter {\n            result.include(id);\n        }\n        result\n    }\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct WidgetRefDef(pub Option<WidgetId>);\n\nimpl From<WidgetRef> for WidgetRefDef {\n    fn from(data: WidgetRef) -> Self {\n        match data.0.read() {\n            Ok(data) => Self(data.clone()),\n            Err(_) => Default::default(),\n        }\n    }\n}\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[serde(from = \"WidgetRefDef\")]\n#[serde(into = \"WidgetRefDef\")]\npub struct WidgetRef(#[serde(skip)] Arc<RwLock<Option<WidgetId>>>);\n\nimpl WidgetRef {\n    pub fn new(id: WidgetId) -> Self {\n        Self(Arc::new(RwLock::new(Some(id))))\n    }\n\n    pub(crate) fn write(&mut self, id: WidgetId) {\n        if let Ok(mut data) = self.0.write() {\n            *data = Some(id);\n        }\n    }\n\n    pub fn read(&self) -> Option<WidgetId> {\n        if let Ok(data) = self.0.read() {\n            data.clone()\n        } else {\n            None\n        }\n    }\n\n    pub fn exists(&self) -> bool {\n        self.0\n            .read()\n            .ok()\n            .map(|data| data.is_some())\n            .unwrap_or_default()\n    }\n}\n\nimpl PartialEq for WidgetRef {\n    fn eq(&self, other: &Self) -> bool {\n        Arc::ptr_eq(&self.0, &other.0)\n    }\n}\n\nimpl From<WidgetRefDef> for WidgetRef {\n    fn from(data: WidgetRefDef) -> Self {\n        WidgetRef(Arc::new(RwLock::new(data.0)))\n    }\n}\n\nimpl std::fmt::Display for WidgetRef {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        if let Ok(id) = self.0.read() {\n            if let Some(id) = id.as_ref() {\n                write!(f, \"{id}\")\n            } else {\n                Ok(())\n            }\n        } else {\n            Ok(())\n        }\n    }\n}\n\n#[derive(PropsData, Debug, Default, Clone, PartialEq, Serialize, Deserialize)]\npub enum WidgetIdOrRef {\n    #[default]\n    None,\n    Id(WidgetId),\n    Ref(WidgetRef),\n}\n\nimpl WidgetIdOrRef {\n    #[inline]\n    pub fn new_ref() -> Self {\n        Self::Ref(WidgetRef::default())\n    }\n\n    #[inline]\n    pub fn is_none(&self) -> bool {\n        matches!(self, Self::None)\n    }\n\n    #[inline]\n    pub fn is_some(&self) -> bool {\n        !self.is_none()\n    }\n\n    pub fn read(&self) -> Option<WidgetId> {\n        match self {\n            Self::None => None,\n            Self::Id(id) => Some(id.to_owned()),\n            Self::Ref(idref) => idref.read(),\n        }\n    }\n}\n\nimpl From<()> for WidgetIdOrRef {\n    fn from(_: ()) -> Self {\n        Self::None\n    }\n}\n\nimpl From<WidgetId> for WidgetIdOrRef {\n    fn from(v: WidgetId) -> Self {\n        Self::Id(v)\n    }\n}\n\nimpl From<WidgetRef> for WidgetIdOrRef {\n    fn from(v: WidgetRef) -> Self {\n        Self::Ref(v)\n    }\n}\n\nimpl std::fmt::Display for WidgetIdOrRef {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Self::None => Ok(()),\n            Self::Id(id) => write!(f, \"{id}\"),\n            Self::Ref(id) => write!(f, \"{id}\"),\n        }\n    }\n}\n\n#[derive(Clone)]\npub enum FnWidget {\n    Pointer(fn(WidgetContext) -> WidgetNode),\n    Closure(Arc<dyn Fn(WidgetContext) -> WidgetNode + Send + Sync>),\n}\n\nimpl FnWidget {\n    pub fn pointer(value: fn(WidgetContext) -> WidgetNode) -> Self {\n        Self::Pointer(value)\n    }\n\n    pub fn closure(value: impl Fn(WidgetContext) -> WidgetNode + Send + Sync + 'static) -> Self {\n        Self::Closure(Arc::new(value))\n    }\n\n    pub fn call(&self, context: WidgetContext) -> WidgetNode {\n        match self {\n            Self::Pointer(value) => value(context),\n            Self::Closure(value) => value(context),\n        }\n    }\n}\n\n#[derive(Default)]\npub struct WidgetLifeCycle {\n    #[allow(clippy::type_complexity)]\n    mount: Vec<Box<dyn FnMut(WidgetMountOrChangeContext) + Send + Sync>>,\n    #[allow(clippy::type_complexity)]\n    change: Vec<Box<dyn FnMut(WidgetMountOrChangeContext) + Send + Sync>>,\n    #[allow(clippy::type_complexity)]\n    unmount: Vec<Box<dyn FnMut(WidgetUnmountContext) + Send + Sync>>,\n}\n\nimpl WidgetLifeCycle {\n    pub fn mount<F>(&mut self, f: F)\n    where\n        F: 'static + FnMut(WidgetMountOrChangeContext) + Send + Sync,\n    {\n        self.mount.push(Box::new(f));\n    }\n\n    pub fn change<F>(&mut self, f: F)\n    where\n        F: 'static + FnMut(WidgetMountOrChangeContext) + Send + Sync,\n    {\n        self.change.push(Box::new(f));\n    }\n\n    pub fn unmount<F>(&mut self, f: F)\n    where\n        F: 'static + FnMut(WidgetUnmountContext) + Send + Sync,\n    {\n        self.unmount.push(Box::new(f));\n    }\n\n    #[allow(clippy::type_complexity)]\n    pub fn unwrap(\n        self,\n    ) -> (\n        Vec<Box<dyn FnMut(WidgetMountOrChangeContext) + Send + Sync>>,\n        Vec<Box<dyn FnMut(WidgetMountOrChangeContext) + Send + Sync>>,\n        Vec<Box<dyn FnMut(WidgetUnmountContext) + Send + Sync>>,\n    ) {\n        let Self {\n            mount,\n            change,\n            unmount,\n        } = self;\n        (mount, change, unmount)\n    }\n}\n\npub fn none_widget(_: WidgetContext) -> WidgetNode {\n    Default::default()\n}\n\npub fn setup(app: &mut Application) {\n    app.register_props::<()>(\"()\");\n    app.register_props::<i8>(\"i8\");\n    app.register_props::<i16>(\"i16\");\n    app.register_props::<i32>(\"i32\");\n    app.register_props::<i64>(\"i64\");\n    app.register_props::<i128>(\"i128\");\n    app.register_props::<u8>(\"u8\");\n    app.register_props::<u16>(\"u16\");\n    app.register_props::<u32>(\"u32\");\n    app.register_props::<u64>(\"u64\");\n    app.register_props::<u128>(\"u128\");\n    app.register_props::<f32>(\"f32\");\n    app.register_props::<f64>(\"f64\");\n    app.register_props::<bool>(\"bool\");\n    app.register_props::<String>(\"String\");\n    app.register_props::<component::containers::anchor_box::AnchorProps>(\"AnchorProps\");\n    app.register_props::<component::containers::anchor_box::PivotBoxProps>(\"PivotBoxProps\");\n    app.register_props::<component::containers::content_box::ContentBoxProps>(\"ContentBoxProps\");\n    app.register_props::<component::containers::flex_box::FlexBoxProps>(\"FlexBoxProps\");\n    app.register_props::<component::containers::float_box::FloatBoxProps>(\"FloatBoxProps\");\n    app.register_props::<component::containers::float_box::FloatBoxNotifyProps>(\n        \"FloatBoxNotifyProps\",\n    );\n    app.register_props::<component::containers::float_box::FloatBoxState>(\"FloatBoxState\");\n    app.register_props::<component::containers::grid_box::GridBoxProps>(\"GridBoxProps\");\n    app.register_props::<component::containers::horizontal_box::HorizontalBoxProps>(\n        \"HorizontalBoxProps\",\n    );\n    app.register_props::<component::containers::hidden_box::HiddenBoxProps>(\"HiddenBoxProps\");\n    app.register_props::<component::containers::scroll_box::ScrollBoxOwner>(\"ScrollBoxOwner\");\n    app.register_props::<component::containers::scroll_box::SideScrollbarsProps>(\n        \"SideScrollbarsProps\",\n    );\n    app.register_props::<component::containers::scroll_box::SideScrollbarsState>(\n        \"SideScrollbarsState\",\n    );\n    app.register_props::<component::containers::portal_box::PortalsContainer>(\"PortalsContainer\");\n    app.register_props::<component::containers::responsive_box::MediaQueryExpression>(\n        \"MediaQueryExpression\",\n    );\n    app.register_props::<component::containers::responsive_box::ResponsiveBoxState>(\n        \"ResponsiveBoxState\",\n    );\n    app.register_props::<component::containers::size_box::SizeBoxProps>(\"SizeBoxProps\");\n    app.register_props::<component::containers::switch_box::SwitchBoxProps>(\"SwitchBoxProps\");\n    app.register_props::<component::containers::tabs_box::TabsBoxProps>(\"TabsBoxProps\");\n    app.register_props::<component::containers::tabs_box::TabPlateProps>(\"TabPlateProps\");\n    app.register_props::<component::containers::tooltip_box::TooltipState>(\"TooltipState\");\n    app.register_props::<component::containers::variant_box::VariantBoxProps>(\"VariantBoxProps\");\n    app.register_props::<component::containers::vertical_box::VerticalBoxProps>(\"VerticalBoxProps\");\n    app.register_props::<component::containers::wrap_box::WrapBoxProps>(\"WrapBoxProps\");\n    app.register_props::<component::image_box::ImageBoxProps>(\"ImageBoxProps\");\n    app.register_props::<component::interactive::button::ButtonProps>(\"ButtonProps\");\n    app.register_props::<component::interactive::button::ButtonNotifyProps>(\"ButtonNotifyProps\");\n    app.register_props::<component::interactive::input_field::TextInputMode>(\"TextInputMode\");\n    app.register_props::<component::interactive::input_field::TextInputProps>(\"TextInputProps\");\n    app.register_props::<component::interactive::input_field::TextInputState>(\"TextInputState\");\n    app.register_props::<component::interactive::input_field::TextInputNotifyProps>(\n        \"TextInputNotifyProps\",\n    );\n    app.register_props::<component::interactive::input_field::TextInputControlNotifyProps>(\n        \"TextInputControlNotifyProps\",\n    );\n    app.register_props::<component::interactive::options_view::OptionsViewMode>(\"OptionsViewMode\");\n    app.register_props::<component::interactive::options_view::OptionsViewProps>(\n        \"OptionsViewProps\",\n    );\n    app.register_props::<component::interactive::slider_view::SliderViewProps>(\"SliderViewProps\");\n    app.register_props::<component::interactive::navigation::NavItemActive>(\"NavItemActive\");\n    app.register_props::<component::interactive::navigation::NavTrackingActive>(\n        \"NavTrackingActive\",\n    );\n    app.register_props::<component::interactive::navigation::NavContainerActive>(\n        \"NavContainerActive\",\n    );\n    app.register_props::<component::interactive::navigation::NavJumpLooped>(\"NavJumpLooped\");\n    app.register_props::<component::interactive::navigation::NavJumpMapProps>(\"NavJumpMapProps\");\n    app.register_props::<component::interactive::scroll_view::ScrollViewState>(\"ScrollViewState\");\n    app.register_props::<component::interactive::scroll_view::ScrollViewRange>(\"ScrollViewRange\");\n    app.register_props::<component::interactive::scroll_view::ScrollViewNotifyProps>(\n        \"ScrollViewNotifyProps\",\n    );\n    app.register_props::<component::MessageForwardProps>(\"MessageForwardProps\");\n    app.register_props::<component::WidgetAlpha>(\"WidgetAlpha\");\n    app.register_props::<component::space_box::SpaceBoxProps>(\"SpaceBoxProps\");\n    app.register_props::<component::text_box::TextBoxProps>(\"TextBoxProps\");\n    app.register_props::<unit::content::ContentBoxItemLayout>(\"ContentBoxItemLayout\");\n    app.register_props::<unit::flex::FlexBoxItemLayout>(\"FlexBoxItemLayout\");\n    app.register_props::<unit::grid::GridBoxItemLayout>(\"GridBoxItemLayout\");\n\n    app.register_component(\"none_widget\", FnWidget::pointer(none_widget));\n    app.register_component(\n        \"area_box\",\n        FnWidget::pointer(component::containers::area_box::area_box),\n    );\n    app.register_component(\n        \"anchor_box\",\n        FnWidget::pointer(component::containers::anchor_box::anchor_box),\n    );\n    app.register_component(\n        \"pivot_box\",\n        FnWidget::pointer(component::containers::anchor_box::pivot_box),\n    );\n    app.register_component(\n        \"nav_content_box\",\n        FnWidget::pointer(component::containers::content_box::nav_content_box),\n    );\n    app.register_component(\n        \"content_box\",\n        FnWidget::pointer(component::containers::content_box::content_box),\n    );\n    app.register_component(\n        \"nav_flex_box\",\n        FnWidget::pointer(component::containers::flex_box::nav_flex_box),\n    );\n    app.register_component(\n        \"flex_box\",\n        FnWidget::pointer(component::containers::flex_box::flex_box),\n    );\n    app.register_component(\n        \"float_box\",\n        FnWidget::pointer(component::containers::float_box::float_box),\n    );\n    app.register_component(\n        \"nav_grid_box\",\n        FnWidget::pointer(component::containers::grid_box::nav_grid_box),\n    );\n    app.register_component(\n        \"grid_box\",\n        FnWidget::pointer(component::containers::grid_box::grid_box),\n    );\n    app.register_component(\n        \"nav_horizontal_box\",\n        FnWidget::pointer(component::containers::horizontal_box::nav_horizontal_box),\n    );\n    app.register_component(\n        \"horizontal_box\",\n        FnWidget::pointer(component::containers::horizontal_box::horizontal_box),\n    );\n    app.register_component(\n        \"nav_scroll_box\",\n        FnWidget::pointer(component::containers::scroll_box::nav_scroll_box),\n    );\n    app.register_component(\n        \"nav_scroll_box_side_scrollbars\",\n        FnWidget::pointer(component::containers::scroll_box::nav_scroll_box_side_scrollbars),\n    );\n    app.register_component(\n        \"portal_box\",\n        FnWidget::pointer(component::containers::portal_box::portal_box),\n    );\n    app.register_component(\n        \"responsive_box\",\n        FnWidget::pointer(component::containers::responsive_box::responsive_box),\n    );\n    app.register_component(\n        \"responsive_props_box\",\n        FnWidget::pointer(component::containers::responsive_box::responsive_props_box),\n    );\n    app.register_component(\n        \"size_box\",\n        FnWidget::pointer(component::containers::size_box::size_box),\n    );\n    app.register_component(\n        \"nav_switch_box\",\n        FnWidget::pointer(component::containers::switch_box::nav_switch_box),\n    );\n    app.register_component(\n        \"switch_box\",\n        FnWidget::pointer(component::containers::switch_box::switch_box),\n    );\n    app.register_component(\n        \"nav_tabs_box\",\n        FnWidget::pointer(component::containers::tabs_box::nav_tabs_box),\n    );\n    app.register_component(\n        \"tooltip_box\",\n        FnWidget::pointer(component::containers::tooltip_box::tooltip_box),\n    );\n    app.register_component(\n        \"portals_tooltip_box\",\n        FnWidget::pointer(component::containers::tooltip_box::portals_tooltip_box),\n    );\n    app.register_component(\n        \"variant_box\",\n        FnWidget::pointer(component::containers::variant_box::variant_box),\n    );\n    app.register_component(\n        \"nav_vertical_box\",\n        FnWidget::pointer(component::containers::vertical_box::nav_vertical_box),\n    );\n    app.register_component(\n        \"vertical_box\",\n        FnWidget::pointer(component::containers::vertical_box::vertical_box),\n    );\n    app.register_component(\n        \"wrap_box\",\n        FnWidget::pointer(component::containers::wrap_box::wrap_box),\n    );\n    app.register_component(\n        \"button\",\n        FnWidget::pointer(component::interactive::button::button),\n    );\n    app.register_component(\n        \"tracked_button\",\n        FnWidget::pointer(component::interactive::button::tracked_button),\n    );\n    app.register_component(\n        \"self_tracked_button\",\n        FnWidget::pointer(component::interactive::button::self_tracked_button),\n    );\n    app.register_component(\n        \"text_input\",\n        FnWidget::pointer(component::interactive::input_field::text_input),\n    );\n    app.register_component(\n        \"input_field\",\n        FnWidget::pointer(component::interactive::input_field::input_field),\n    );\n    app.register_component(\n        \"options_view\",\n        FnWidget::pointer(component::interactive::options_view::options_view),\n    );\n    app.register_component(\n        \"slider_view\",\n        FnWidget::pointer(component::interactive::slider_view::slider_view),\n    );\n    app.register_component(\n        \"navigation_barrier\",\n        FnWidget::pointer(component::interactive::navigation::navigation_barrier),\n    );\n    app.register_component(\n        \"tracking\",\n        FnWidget::pointer(component::interactive::navigation::tracking),\n    );\n    app.register_component(\n        \"self_tracking\",\n        FnWidget::pointer(component::interactive::navigation::self_tracking),\n    );\n    app.register_component(\n        \"space_box\",\n        FnWidget::pointer(component::space_box::space_box),\n    );\n    app.register_component(\n        \"image_box\",\n        FnWidget::pointer(component::image_box::image_box),\n    );\n    app.register_component(\"text_box\", FnWidget::pointer(component::text_box::text_box));\n}\n\n/// Helper to manually create a [`WidgetComponent`][crate::widget::component::WidgetComponent]\n/// struct from a function.\n///\n/// Users will not usually need this macro, but it can be useful in some advanced cases or where you\n/// don't want to use the [`widget`] macro.\n/// ```\n#[macro_export]\nmacro_rules! make_widget {\n    ($type_id:path) => {{\n        let processor = $type_id;\n        let type_name = stringify!($type_id);\n        $crate::widget::component::WidgetComponent::new(\n            $crate::widget::FnWidget::pointer(processor),\n            type_name,\n        )\n    }};\n}\n\n/// A helper for getting the named children out of a widget context.\n#[macro_export]\nmacro_rules! unpack_named_slots {\n    ($map:expr => $name:ident) => {\n        #[allow(unused_mut)]\n        let mut $name = {\n            let mut map = $map;\n            match map.remove(stringify!($name)) {\n                Some(widget) => widget,\n                None => $crate::widget::node::WidgetNode::None,\n            }\n        };\n    };\n    ($map:expr => { $($name:ident),+ }) => {\n        #[allow(unused_mut)]\n        let ( $( mut $name ),+ ) = {\n            let mut map = $map;\n            (\n                $(\n                    {\n                        match map.remove(stringify!($name)) {\n                            Some(widget) => widget,\n                            None => $crate::widget::node::WidgetNode::None,\n                        }\n                    }\n                ),+\n            )\n        };\n    };\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_widget_id() {\n        let id = WidgetId::empty();\n        assert_eq!(id.type_name(), \"\");\n        assert_eq!(id.path(), \"\");\n        assert_eq!(id.depth(), 0);\n\n        let id = WidgetId::new(\"type\", &[\"parent\".into(), \"me\".into()]);\n        assert_eq!(id.to_string(), \"type:parent/me\".to_owned());\n        assert_eq!(id.type_name(), \"type\");\n        assert_eq!(id.parts().next().unwrap(), \"parent\");\n        assert_eq!(id.key(), \"me\");\n        assert_eq!(id.clone(), id);\n\n        let a = WidgetId::from_str(\"a:root/a\").unwrap();\n        let b = WidgetId::from_str(\"b:root/b\").unwrap();\n        let mut common = WidgetIdCommon::default();\n        assert_eq!(common.path(), None);\n        common.include(&a);\n        assert_eq!(common.path(), Some(\"root/a\"));\n        let mut common = WidgetIdCommon::default();\n        common.include(&b);\n        assert_eq!(common.path(), Some(\"root/b\"));\n        common.include(&a);\n        assert_eq!(common.path(), Some(\"root\"));\n\n        let id = WidgetId::from_str(\"type:parent/me\").unwrap();\n        assert_eq!(&*id, \"type:parent/me\");\n        assert_eq!(id.path(), \"parent/me\");\n        let id = id.pop();\n        assert_eq!(&*id, \"type:parent\");\n        assert_eq!(id.path(), \"parent\");\n        let id = id.pop();\n        assert_eq!(&*id, \"type:\");\n        assert_eq!(id.path(), \"\");\n        let id = id.push(\"parent\");\n        assert_eq!(&*id, \"type:parent\");\n        assert_eq!(id.path(), \"parent\");\n        let id = id.push(\"me\");\n        assert_eq!(&*id, \"type:parent/me\");\n        assert_eq!(id.path(), \"parent/me\");\n        assert_eq!(id.key(), \"me\");\n        assert_eq!(id.meta(), \"\");\n        let id = id.push(\"with?meta\");\n        assert_eq!(&*id, \"type:parent/me/with?meta\");\n        assert_eq!(id.path(), \"parent/me/with?meta\");\n        assert_eq!(id.key(), \"with\");\n        assert_eq!(id.meta(), \"meta\");\n\n        let a = WidgetId::from_str(\"a:root/a/b/c\").unwrap();\n        let b = WidgetId::from_str(\"b:root/a/b/c\").unwrap();\n        assert_eq!(a.distance_to(&b), Ok(0));\n        assert!(!a.is_subset_of(&b));\n        assert!(!a.is_superset_of(&b));\n        let a = WidgetId::from_str(\"a:root/a/b\").unwrap();\n        let b = WidgetId::from_str(\"b:root/a/b/c\").unwrap();\n        assert_eq!(a.distance_to(&b), Ok(-1));\n        assert!(a.is_subset_of(&b));\n        assert!(!a.is_superset_of(&b));\n        let a = WidgetId::from_str(\"a:root/a\").unwrap();\n        let b = WidgetId::from_str(\"b:root/a/b/c\").unwrap();\n        assert_eq!(a.distance_to(&b), Ok(-2));\n        assert!(a.is_subset_of(&b));\n        assert!(!a.is_superset_of(&b));\n        let a = WidgetId::from_str(\"a:root\").unwrap();\n        let b = WidgetId::from_str(\"b:root/a/b/c\").unwrap();\n        assert_eq!(a.distance_to(&b), Ok(-3));\n        assert!(a.is_subset_of(&b));\n        assert!(!a.is_superset_of(&b));\n        let a = WidgetId::from_str(\"a:root/a/b/c\").unwrap();\n        let b = WidgetId::from_str(\"b:root/a/b\").unwrap();\n        assert_eq!(a.distance_to(&b), Ok(1));\n        assert!(!a.is_subset_of(&b));\n        assert!(a.is_superset_of(&b));\n        let a = WidgetId::from_str(\"a:root/a/b/c\").unwrap();\n        let b = WidgetId::from_str(\"b:root/a\").unwrap();\n        assert_eq!(a.distance_to(&b), Ok(2));\n        assert!(!a.is_subset_of(&b));\n        assert!(a.is_superset_of(&b));\n        let a = WidgetId::from_str(\"a:root/a/b/c\").unwrap();\n        let b = WidgetId::from_str(\"b:root\").unwrap();\n        assert_eq!(a.distance_to(&b), Ok(3));\n        assert!(!a.is_subset_of(&b));\n        assert!(a.is_superset_of(&b));\n        let a = WidgetId::from_str(\"a:root/a/b/x\").unwrap();\n        let b = WidgetId::from_str(\"b:root/a/b/c\").unwrap();\n        assert_eq!(a.distance_to(&b), Err(-1));\n        assert!(!a.is_subset_of(&b));\n        assert!(!a.is_superset_of(&b));\n        let a = WidgetId::from_str(\"a:root/a/x/y\").unwrap();\n        let b = WidgetId::from_str(\"b:root/a/b/c\").unwrap();\n        assert_eq!(a.distance_to(&b), Err(-2));\n        assert!(!a.is_subset_of(&b));\n        assert!(!a.is_superset_of(&b));\n        let a = WidgetId::from_str(\"a:root/x/y/z\").unwrap();\n        let b = WidgetId::from_str(\"b:root/a/b/c\").unwrap();\n        assert_eq!(a.distance_to(&b), Err(-3));\n        assert!(!a.is_subset_of(&b));\n        assert!(!a.is_superset_of(&b));\n    }\n}\n"
  },
  {
    "path": "crates/core/src/widget/node.rs",
    "content": "use crate::{\n    Prefab,\n    props::Props,\n    widget::{\n        component::{WidgetComponent, WidgetComponentPrefab},\n        unit::{WidgetUnitNode, WidgetUnitNodePrefab},\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[allow(clippy::large_enum_variant)]\n#[derive(Debug, Default, Clone)]\npub enum WidgetNode {\n    #[default]\n    None,\n    Component(WidgetComponent),\n    Unit(WidgetUnitNode),\n    Tuple(Vec<WidgetNode>),\n}\n\nimpl WidgetNode {\n    pub fn is_none(&self) -> bool {\n        match self {\n            Self::None => true,\n            Self::Unit(unit) => unit.is_none(),\n            Self::Tuple(v) => v.is_empty(),\n            _ => false,\n        }\n    }\n\n    pub fn is_some(&self) -> bool {\n        match self {\n            Self::None => false,\n            Self::Unit(unit) => unit.is_some(),\n            Self::Tuple(v) => !v.is_empty(),\n            _ => true,\n        }\n    }\n\n    pub fn as_component(&self) -> Option<&WidgetComponent> {\n        match self {\n            Self::Component(c) => Some(c),\n            _ => None,\n        }\n    }\n\n    pub fn as_unit(&self) -> Option<&WidgetUnitNode> {\n        match self {\n            Self::Unit(u) => Some(u),\n            _ => None,\n        }\n    }\n\n    pub fn as_tuple(&self) -> Option<&[WidgetNode]> {\n        match self {\n            Self::Tuple(v) => Some(v),\n            _ => None,\n        }\n    }\n\n    pub fn props(&self) -> Option<&Props> {\n        match self {\n            Self::Component(c) => Some(&c.props),\n            Self::Unit(u) => u.props(),\n            _ => None,\n        }\n    }\n\n    pub fn props_mut(&mut self) -> Option<&mut Props> {\n        match self {\n            Self::Component(c) => Some(&mut c.props),\n            Self::Unit(u) => u.props_mut(),\n            _ => None,\n        }\n    }\n\n    pub fn remap_props<F>(&mut self, f: F)\n    where\n        F: FnMut(Props) -> Props,\n    {\n        match self {\n            Self::Component(c) => c.remap_props(f),\n            Self::Unit(u) => u.remap_props(f),\n            _ => {}\n        }\n    }\n\n    pub fn shared_props(&self) -> Option<&Props> {\n        match self {\n            Self::Component(c) => c.shared_props.as_ref(),\n            _ => None,\n        }\n    }\n\n    pub fn shared_props_mut(&mut self) -> Option<&mut Props> {\n        match self {\n            Self::Component(c) => {\n                if c.shared_props.is_none() {\n                    c.shared_props = Some(Default::default());\n                }\n                c.shared_props.as_mut()\n            }\n            _ => None,\n        }\n    }\n\n    pub fn remap_shared_props<F>(&mut self, f: F)\n    where\n        F: FnMut(Props) -> Props,\n    {\n        if let Self::Component(c) = self {\n            c.remap_shared_props(f);\n        }\n    }\n\n    pub fn pack_tuple<const N: usize>(data: [WidgetNode; N]) -> Self {\n        Self::Tuple(data.into())\n    }\n\n    pub fn unpack_tuple<const N: usize>(self) -> [WidgetNode; N] {\n        if let WidgetNode::Tuple(mut data) = self {\n            let mut iter = data.drain(..);\n            std::array::from_fn(|_| iter.next().unwrap_or_default())\n        } else {\n            std::array::from_fn(|_| WidgetNode::None)\n        }\n    }\n}\n\nimpl From<()> for WidgetNode {\n    fn from(_: ()) -> Self {\n        Self::None\n    }\n}\n\nimpl From<()> for Box<WidgetNode> {\n    fn from(_: ()) -> Self {\n        Box::new(WidgetNode::None)\n    }\n}\n\nimpl From<WidgetComponent> for WidgetNode {\n    fn from(component: WidgetComponent) -> Self {\n        Self::Component(component)\n    }\n}\n\nimpl From<WidgetUnitNode> for WidgetNode {\n    fn from(unit: WidgetUnitNode) -> Self {\n        Self::Unit(unit)\n    }\n}\n\nimpl From<WidgetUnitNode> for Box<WidgetNode> {\n    fn from(unit: WidgetUnitNode) -> Self {\n        Box::new(WidgetNode::Unit(unit))\n    }\n}\n\nimpl<const N: usize> From<[WidgetNode; N]> for WidgetNode {\n    fn from(data: [WidgetNode; N]) -> Self {\n        Self::pack_tuple(data)\n    }\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub(crate) enum WidgetNodePrefab {\n    #[default]\n    None,\n    Component(WidgetComponentPrefab),\n    Unit(WidgetUnitNodePrefab),\n    Tuple(Vec<WidgetNodePrefab>),\n}\n\nimpl Prefab for WidgetNodePrefab {}\n"
  },
  {
    "path": "crates/core/src/widget/unit/area.rs",
    "content": "use crate::widget::{\n    WidgetId,\n    node::{WidgetNode, WidgetNodePrefab},\n    unit::{WidgetUnit, WidgetUnitData},\n};\nuse serde::{Deserialize, Serialize};\nuse std::convert::TryFrom;\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct AreaBox {\n    #[serde(default)]\n    pub id: WidgetId,\n    #[serde(default)]\n    pub slot: Box<WidgetUnit>,\n}\n\nimpl WidgetUnitData for AreaBox {\n    fn id(&self) -> &WidgetId {\n        &self.id\n    }\n\n    fn get_children(&self) -> Vec<&WidgetUnit> {\n        vec![&self.slot]\n    }\n}\n\nimpl TryFrom<AreaBoxNode> for AreaBox {\n    type Error = ();\n\n    fn try_from(node: AreaBoxNode) -> Result<Self, Self::Error> {\n        let AreaBoxNode { id, slot } = node;\n        Ok(Self {\n            id,\n            slot: Box::new(WidgetUnit::try_from(*slot)?),\n        })\n    }\n}\n\n#[derive(Debug, Default, Clone)]\npub struct AreaBoxNode {\n    pub id: WidgetId,\n    pub slot: Box<WidgetNode>,\n}\n\nimpl From<AreaBoxNode> for WidgetNode {\n    fn from(data: AreaBoxNode) -> Self {\n        Self::Unit(data.into())\n    }\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub(crate) struct AreaBoxNodePrefab {\n    #[serde(default)]\n    pub id: WidgetId,\n    #[serde(default)]\n    pub slot: Box<WidgetNodePrefab>,\n}\n"
  },
  {
    "path": "crates/core/src/widget/unit/content.rs",
    "content": "use crate::{\n    PrefabValue, PropsData, Scalar,\n    props::Props,\n    widget::{\n        WidgetId,\n        node::{WidgetNode, WidgetNodePrefab},\n        unit::{WidgetUnit, WidgetUnitData},\n        utils::{Rect, Transform, Vec2},\n    },\n};\nuse serde::{Deserialize, Serialize};\nuse std::convert::TryFrom;\n\n#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]\npub struct ContentBoxItemPreserveInBounds {\n    #[serde(default)]\n    pub width: bool,\n    #[serde(default)]\n    pub height: bool,\n}\n\nimpl From<bool> for ContentBoxItemPreserveInBounds {\n    fn from(value: bool) -> Self {\n        Self {\n            width: value,\n            height: value,\n        }\n    }\n}\n\n#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]\npub struct ContentBoxItemCutInBounds {\n    #[serde(default)]\n    pub left: bool,\n    #[serde(default)]\n    pub right: bool,\n    #[serde(default)]\n    pub top: bool,\n    #[serde(default)]\n    pub bottom: bool,\n}\n\nimpl From<bool> for ContentBoxItemCutInBounds {\n    fn from(value: bool) -> Self {\n        Self {\n            left: value,\n            right: value,\n            top: value,\n            bottom: value,\n        }\n    }\n}\n\n#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]\npub struct ContentBoxItemKeepInBounds {\n    #[serde(default)]\n    pub preserve: ContentBoxItemPreserveInBounds,\n    #[serde(default)]\n    pub cut: ContentBoxItemCutInBounds,\n}\n\nimpl From<bool> for ContentBoxItemKeepInBounds {\n    fn from(value: bool) -> Self {\n        Self {\n            preserve: value.into(),\n            cut: value.into(),\n        }\n    }\n}\n\n/// Allows customizing how an item in a [`content_box`] is laid out\n///\n/// [`content_box`]: crate::widget::component::containers::content_box::content_box\n#[derive(PropsData, Debug, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct ContentBoxItemLayout {\n    #[serde(default = \"ContentBoxItemLayout::default_anchors\")]\n    pub anchors: Rect,\n    /// The margins to put on each side of the item\n    #[serde(default)]\n    pub margin: Rect,\n    /// Tells in percentage, where is the center of mass of the widget, relative to it's box size\n    #[serde(default)]\n    pub align: Vec2,\n    /// The amount to offset the item from where it would otherwise be laid out\n    #[serde(default)]\n    pub offset: Vec2,\n    /// The \"Z\" depth of the item\n    #[serde(default)]\n    pub depth: Scalar,\n    /// Set of constraints that tell if and how to keep item in container bounds\n    #[serde(default)]\n    pub keep_in_bounds: ContentBoxItemKeepInBounds,\n}\n\nimpl ContentBoxItemLayout {\n    fn default_anchors() -> Rect {\n        Rect {\n            left: 0.0,\n            right: 1.0,\n            top: 0.0,\n            bottom: 1.0,\n        }\n    }\n}\n\nimpl Default for ContentBoxItemLayout {\n    fn default() -> Self {\n        Self {\n            anchors: Self::default_anchors(),\n            margin: Default::default(),\n            align: Default::default(),\n            offset: Default::default(),\n            depth: 0.0,\n            keep_in_bounds: Default::default(),\n        }\n    }\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct ContentBoxItem {\n    #[serde(default)]\n    pub slot: WidgetUnit,\n    #[serde(default)]\n    pub layout: ContentBoxItemLayout,\n}\n\nimpl TryFrom<ContentBoxItemNode> for ContentBoxItem {\n    type Error = ();\n\n    fn try_from(node: ContentBoxItemNode) -> Result<Self, Self::Error> {\n        let ContentBoxItemNode { slot, layout } = node;\n        Ok(Self {\n            slot: WidgetUnit::try_from(slot)?,\n            layout,\n        })\n    }\n}\n\n#[derive(Debug, Default, Clone)]\npub struct ContentBoxItemNode {\n    pub slot: WidgetNode,\n    pub layout: ContentBoxItemLayout,\n}\n\n#[derive(Debug, Clone, Copy, Serialize, Deserialize)]\npub struct ContentBoxContentReposition {\n    #[serde(default)]\n    pub offset: Vec2,\n    #[serde(default = \"ContentBoxContentReposition::default_scale\")]\n    pub scale: Vec2,\n}\n\nimpl Default for ContentBoxContentReposition {\n    fn default() -> Self {\n        Self {\n            offset: Default::default(),\n            scale: Self::default_scale(),\n        }\n    }\n}\n\nimpl ContentBoxContentReposition {\n    fn default_scale() -> Vec2 {\n        1.0.into()\n    }\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct ContentBox {\n    #[serde(default)]\n    pub id: WidgetId,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"Vec::is_empty\")]\n    pub items: Vec<ContentBoxItem>,\n    #[serde(default)]\n    pub clipping: bool,\n    #[serde(default)]\n    pub content_reposition: ContentBoxContentReposition,\n    #[serde(default)]\n    pub transform: Transform,\n}\n\nimpl WidgetUnitData for ContentBox {\n    fn id(&self) -> &WidgetId {\n        &self.id\n    }\n\n    fn get_children(&self) -> Vec<&WidgetUnit> {\n        self.items.iter().map(|item| &item.slot).collect()\n    }\n}\n\nimpl TryFrom<ContentBoxNode> for ContentBox {\n    type Error = ();\n\n    fn try_from(node: ContentBoxNode) -> Result<Self, Self::Error> {\n        let ContentBoxNode {\n            id,\n            items,\n            clipping,\n            content_reposition,\n            transform,\n            ..\n        } = node;\n        let items = items\n            .into_iter()\n            .map(ContentBoxItem::try_from)\n            .collect::<Result<_, _>>()?;\n        Ok(Self {\n            id,\n            items,\n            clipping,\n            content_reposition,\n            transform,\n        })\n    }\n}\n\n#[derive(Debug, Default, Clone)]\npub struct ContentBoxNode {\n    pub id: WidgetId,\n    pub props: Props,\n    pub items: Vec<ContentBoxItemNode>,\n    pub clipping: bool,\n    pub content_reposition: ContentBoxContentReposition,\n    pub transform: Transform,\n}\n\nimpl ContentBoxNode {\n    pub fn remap_props<F>(&mut self, mut f: F)\n    where\n        F: FnMut(Props) -> Props,\n    {\n        let props = std::mem::take(&mut self.props);\n        self.props = (f)(props);\n    }\n}\n\nimpl From<ContentBoxNode> for WidgetNode {\n    fn from(data: ContentBoxNode) -> Self {\n        Self::Unit(data.into())\n    }\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub(crate) struct ContentBoxNodePrefab {\n    #[serde(default)]\n    pub id: WidgetId,\n    #[serde(default)]\n    pub props: PrefabValue,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"Vec::is_empty\")]\n    pub items: Vec<ContentBoxItemNodePrefab>,\n    #[serde(default)]\n    pub clipping: bool,\n    #[serde(default)]\n    pub content_reposition: ContentBoxContentReposition,\n    #[serde(default)]\n    pub transform: Transform,\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub(crate) struct ContentBoxItemNodePrefab {\n    #[serde(default)]\n    pub slot: WidgetNodePrefab,\n    #[serde(default)]\n    pub layout: ContentBoxItemLayout,\n}\n"
  },
  {
    "path": "crates/core/src/widget/unit/flex.rs",
    "content": "use crate::{\n    PrefabValue, PropsData, Scalar,\n    props::Props,\n    widget::{\n        WidgetId,\n        node::{WidgetNode, WidgetNodePrefab},\n        unit::{WidgetUnit, WidgetUnitData},\n        utils::{Rect, Transform},\n    },\n};\nuse serde::{Deserialize, Serialize};\nuse std::convert::TryFrom;\n\n#[derive(PropsData, Debug, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct FlexBoxItemLayout {\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub basis: Option<Scalar>,\n    #[serde(default = \"FlexBoxItemLayout::default_fill\")]\n    pub fill: Scalar,\n    #[serde(default = \"FlexBoxItemLayout::default_grow\")]\n    pub grow: Scalar,\n    #[serde(default = \"FlexBoxItemLayout::default_shrink\")]\n    pub shrink: Scalar,\n    #[serde(default)]\n    pub margin: Rect,\n    #[serde(default)]\n    pub align: Scalar,\n}\n\nimpl FlexBoxItemLayout {\n    fn default_fill() -> Scalar {\n        1.0\n    }\n\n    fn default_grow() -> Scalar {\n        1.0\n    }\n\n    fn default_shrink() -> Scalar {\n        1.0\n    }\n\n    pub fn cleared() -> Self {\n        Self {\n            fill: 0.0,\n            grow: 0.0,\n            shrink: 0.0,\n            ..Default::default()\n        }\n    }\n\n    pub fn no_growing_and_shrinking() -> Self {\n        Self {\n            grow: 0.0,\n            shrink: 1.0,\n            ..Default::default()\n        }\n    }\n}\n\nimpl Default for FlexBoxItemLayout {\n    fn default() -> Self {\n        Self {\n            basis: None,\n            fill: Self::default_fill(),\n            grow: Self::default_grow(),\n            shrink: Self::default_shrink(),\n            margin: Default::default(),\n            align: 0.0,\n        }\n    }\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct FlexBoxItem {\n    #[serde(default)]\n    pub slot: WidgetUnit,\n    #[serde(default)]\n    pub layout: FlexBoxItemLayout,\n}\n\nimpl TryFrom<FlexBoxItemNode> for FlexBoxItem {\n    type Error = ();\n\n    fn try_from(node: FlexBoxItemNode) -> Result<Self, Self::Error> {\n        let FlexBoxItemNode { slot, layout } = node;\n        Ok(Self {\n            slot: WidgetUnit::try_from(slot)?,\n            layout,\n        })\n    }\n}\n\n#[derive(Debug, Default, Clone)]\npub struct FlexBoxItemNode {\n    pub slot: WidgetNode,\n    pub layout: FlexBoxItemLayout,\n}\n\n#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]\npub enum FlexBoxDirection {\n    #[default]\n    HorizontalLeftToRight,\n    HorizontalRightToLeft,\n    VerticalTopToBottom,\n    VerticalBottomToTop,\n}\n\nimpl FlexBoxDirection {\n    pub fn is_horizontal(&self) -> bool {\n        *self == Self::HorizontalLeftToRight || *self == Self::HorizontalRightToLeft\n    }\n\n    pub fn is_vertical(&self) -> bool {\n        *self == Self::VerticalTopToBottom || *self == Self::VerticalBottomToTop\n    }\n\n    pub fn is_order_ascending(&self) -> bool {\n        *self == Self::HorizontalLeftToRight || *self == Self::VerticalTopToBottom\n    }\n\n    pub fn is_order_descending(&self) -> bool {\n        *self == Self::HorizontalRightToLeft || *self == Self::VerticalBottomToTop\n    }\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct FlexBox {\n    #[serde(default)]\n    pub id: WidgetId,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"Vec::is_empty\")]\n    pub items: Vec<FlexBoxItem>,\n    #[serde(default)]\n    pub direction: FlexBoxDirection,\n    #[serde(default)]\n    pub separation: Scalar,\n    #[serde(default)]\n    pub wrap: bool,\n    #[serde(default)]\n    pub transform: Transform,\n}\n\nimpl WidgetUnitData for FlexBox {\n    fn id(&self) -> &WidgetId {\n        &self.id\n    }\n\n    fn get_children(&self) -> Vec<&WidgetUnit> {\n        self.items.iter().map(|item| &item.slot).collect()\n    }\n}\n\nimpl TryFrom<FlexBoxNode> for FlexBox {\n    type Error = ();\n\n    fn try_from(node: FlexBoxNode) -> Result<Self, Self::Error> {\n        let FlexBoxNode {\n            id,\n            items,\n            direction,\n            separation,\n            wrap,\n            transform,\n            ..\n        } = node;\n        let items = items\n            .into_iter()\n            .map(FlexBoxItem::try_from)\n            .collect::<Result<_, _>>()?;\n        Ok(Self {\n            id,\n            items,\n            direction,\n            separation,\n            wrap,\n            transform,\n        })\n    }\n}\n\n#[derive(Debug, Default, Clone)]\npub struct FlexBoxNode {\n    pub id: WidgetId,\n    pub props: Props,\n    pub items: Vec<FlexBoxItemNode>,\n    pub direction: FlexBoxDirection,\n    pub separation: Scalar,\n    pub wrap: bool,\n    pub transform: Transform,\n}\n\nimpl FlexBoxNode {\n    pub fn remap_props<F>(&mut self, mut f: F)\n    where\n        F: FnMut(Props) -> Props,\n    {\n        let props = std::mem::take(&mut self.props);\n        self.props = (f)(props);\n    }\n}\n\nimpl From<FlexBoxNode> for WidgetNode {\n    fn from(data: FlexBoxNode) -> Self {\n        Self::Unit(data.into())\n    }\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub(crate) struct FlexBoxNodePrefab {\n    #[serde(default)]\n    pub id: WidgetId,\n    #[serde(default)]\n    pub props: PrefabValue,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"Vec::is_empty\")]\n    pub items: Vec<FlexBoxItemNodePrefab>,\n    #[serde(default)]\n    pub direction: FlexBoxDirection,\n    #[serde(default)]\n    pub separation: Scalar,\n    #[serde(default)]\n    pub wrap: bool,\n    #[serde(default)]\n    pub transform: Transform,\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub(crate) struct FlexBoxItemNodePrefab {\n    #[serde(default)]\n    pub slot: WidgetNodePrefab,\n    #[serde(default)]\n    pub layout: FlexBoxItemLayout,\n}\n"
  },
  {
    "path": "crates/core/src/widget/unit/grid.rs",
    "content": "use crate::{\n    PrefabValue, PropsData, Scalar,\n    props::Props,\n    widget::{\n        WidgetId,\n        node::{WidgetNode, WidgetNodePrefab},\n        unit::{WidgetUnit, WidgetUnitData},\n        utils::{IntRect, Rect, Transform},\n    },\n};\nuse serde::{Deserialize, Serialize};\nuse std::convert::TryFrom;\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct GridBoxItemLayout {\n    #[serde(default)]\n    pub space_occupancy: IntRect,\n    #[serde(default)]\n    pub margin: Rect,\n    #[serde(default)]\n    pub horizontal_align: Scalar,\n    #[serde(default)]\n    pub vertical_align: Scalar,\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct GridBoxItem {\n    #[serde(default)]\n    pub slot: WidgetUnit,\n    #[serde(default)]\n    pub layout: GridBoxItemLayout,\n}\n\nimpl TryFrom<GridBoxItemNode> for GridBoxItem {\n    type Error = ();\n\n    fn try_from(node: GridBoxItemNode) -> Result<Self, Self::Error> {\n        let GridBoxItemNode { slot, layout } = node;\n        Ok(Self {\n            slot: WidgetUnit::try_from(slot)?,\n            layout,\n        })\n    }\n}\n\n#[derive(Debug, Default, Clone)]\npub struct GridBoxItemNode {\n    pub slot: WidgetNode,\n    pub layout: GridBoxItemLayout,\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct GridBox {\n    #[serde(default)]\n    pub id: WidgetId,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"Vec::is_empty\")]\n    pub items: Vec<GridBoxItem>,\n    #[serde(default)]\n    pub cols: usize,\n    #[serde(default)]\n    pub rows: usize,\n    #[serde(default)]\n    pub transform: Transform,\n}\n\nimpl WidgetUnitData for GridBox {\n    fn id(&self) -> &WidgetId {\n        &self.id\n    }\n\n    fn get_children(&self) -> Vec<&WidgetUnit> {\n        self.items.iter().map(|item| &item.slot).collect()\n    }\n}\n\nimpl TryFrom<GridBoxNode> for GridBox {\n    type Error = ();\n\n    fn try_from(node: GridBoxNode) -> Result<Self, Self::Error> {\n        let GridBoxNode {\n            id,\n            items,\n            cols,\n            rows,\n            transform,\n            ..\n        } = node;\n        let items = items\n            .into_iter()\n            .map(GridBoxItem::try_from)\n            .collect::<Result<_, _>>()?;\n        Ok(Self {\n            id,\n            items,\n            cols,\n            rows,\n            transform,\n        })\n    }\n}\n\n#[derive(Debug, Default, Clone)]\npub struct GridBoxNode {\n    pub id: WidgetId,\n    pub props: Props,\n    pub items: Vec<GridBoxItemNode>,\n    pub cols: usize,\n    pub rows: usize,\n    pub transform: Transform,\n}\n\nimpl GridBoxNode {\n    pub fn remap_props<F>(&mut self, mut f: F)\n    where\n        F: FnMut(Props) -> Props,\n    {\n        let props = std::mem::take(&mut self.props);\n        self.props = (f)(props);\n    }\n}\n\nimpl From<GridBoxNode> for WidgetNode {\n    fn from(data: GridBoxNode) -> Self {\n        Self::Unit(data.into())\n    }\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub(crate) struct GridBoxNodePrefab {\n    #[serde(default)]\n    pub id: WidgetId,\n    #[serde(default)]\n    pub props: PrefabValue,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"Vec::is_empty\")]\n    pub items: Vec<GridBoxItemNodePrefab>,\n    #[serde(default)]\n    pub cols: usize,\n    #[serde(default)]\n    pub rows: usize,\n    #[serde(default)]\n    pub transform: Transform,\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub(crate) struct GridBoxItemNodePrefab {\n    #[serde(default)]\n    pub slot: WidgetNodePrefab,\n    #[serde(default)]\n    pub layout: GridBoxItemLayout,\n}\n"
  },
  {
    "path": "crates/core/src/widget/unit/image.rs",
    "content": "use crate::{\n    PrefabValue, Scalar,\n    layout::CoordsMappingScaling,\n    props::Props,\n    widget::{\n        WidgetId,\n        node::WidgetNode,\n        unit::WidgetUnitData,\n        utils::{Color, Rect, Transform, Vec2},\n    },\n};\nuse serde::{Deserialize, Serialize};\nuse std::{collections::HashMap, convert::TryFrom, sync::Arc};\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct ImageBoxFrame {\n    #[serde(default)]\n    pub source: Rect,\n    #[serde(default)]\n    pub destination: Rect,\n    #[serde(default)]\n    pub frame_only: bool,\n    #[serde(default)]\n    pub frame_keep_aspect_ratio: bool,\n}\n\nimpl From<Scalar> for ImageBoxFrame {\n    fn from(v: Scalar) -> Self {\n        Self {\n            source: v.into(),\n            destination: v.into(),\n            frame_only: false,\n            frame_keep_aspect_ratio: false,\n        }\n    }\n}\n\nimpl From<(Scalar, bool)> for ImageBoxFrame {\n    fn from((v, fo): (Scalar, bool)) -> Self {\n        Self {\n            source: v.into(),\n            destination: v.into(),\n            frame_only: fo,\n            frame_keep_aspect_ratio: false,\n        }\n    }\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub enum ImageBoxImageScaling {\n    #[default]\n    Stretch,\n    Frame(ImageBoxFrame),\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct ImageBoxColor {\n    #[serde(default)]\n    pub color: Color,\n    #[serde(default)]\n    pub scaling: ImageBoxImageScaling,\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct ImageBoxImage {\n    #[serde(default)]\n    pub id: String,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub source_rect: Option<Rect>,\n    #[serde(default)]\n    pub scaling: ImageBoxImageScaling,\n    #[serde(default = \"ImageBoxImage::default_tint\")]\n    pub tint: Color,\n}\n\nimpl Default for ImageBoxImage {\n    fn default() -> Self {\n        Self {\n            id: Default::default(),\n            source_rect: Default::default(),\n            scaling: Default::default(),\n            tint: Self::default_tint(),\n        }\n    }\n}\n\nimpl ImageBoxImage {\n    fn default_tint() -> Color {\n        Color {\n            r: 1.0,\n            g: 1.0,\n            b: 1.0,\n            a: 1.0,\n        }\n    }\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct ImageBoxProceduralVertex {\n    #[serde(default)]\n    pub position: Vec2,\n    #[serde(default)]\n    pub page: Scalar,\n    #[serde(default)]\n    pub tex_coord: Vec2,\n    #[serde(default)]\n    pub color: Color,\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct ImageBoxProceduralMeshData {\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"Vec::is_empty\")]\n    pub vertices: Vec<ImageBoxProceduralVertex>,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"Vec::is_empty\")]\n    pub triangles: Vec<[u32; 3]>,\n}\n\n#[derive(Clone, Serialize, Deserialize)]\npub enum ImageBoxProceduralMesh {\n    Owned(ImageBoxProceduralMeshData),\n    Shared(Arc<ImageBoxProceduralMeshData>),\n    /// fn(widget local space rect, procedural image parameters map) -> mesh data\n    #[serde(skip)]\n    #[allow(clippy::type_complexity)]\n    Generator(\n        Arc<\n            dyn Fn(Rect, &HashMap<String, Scalar>) -> ImageBoxProceduralMeshData\n                + 'static\n                + Send\n                + Sync,\n        >,\n    ),\n}\n\nimpl std::fmt::Debug for ImageBoxProceduralMesh {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Self::Owned(data) => write!(f, \"Owned({data:?})\"),\n            Self::Shared(data) => write!(f, \"Shared({data:?})\"),\n            Self::Generator(_) => write!(f, \"Generator(...)\"),\n        }\n    }\n}\n\nimpl Default for ImageBoxProceduralMesh {\n    fn default() -> Self {\n        Self::Owned(Default::default())\n    }\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct ImageBoxProcedural {\n    #[serde(default)]\n    pub id: String,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"HashMap::is_empty\")]\n    pub parameters: HashMap<String, Scalar>,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"Vec::is_empty\")]\n    pub images: Vec<String>,\n    #[serde(default)]\n    pub mesh: ImageBoxProceduralMesh,\n    #[serde(default)]\n    pub vertex_mapping: CoordsMappingScaling,\n}\n\nimpl ImageBoxProcedural {\n    pub fn new(id: impl ToString) -> Self {\n        Self {\n            id: id.to_string(),\n            parameters: Default::default(),\n            images: Default::default(),\n            mesh: Default::default(),\n            vertex_mapping: Default::default(),\n        }\n    }\n\n    pub fn param(mut self, id: impl ToString, value: Scalar) -> Self {\n        self.parameters.insert(id.to_string(), value);\n        self\n    }\n\n    pub fn image(mut self, id: impl ToString) -> Self {\n        self.images.push(id.to_string());\n        self\n    }\n\n    pub fn mesh(mut self, mesh: ImageBoxProceduralMesh) -> Self {\n        self.mesh = mesh;\n        self\n    }\n\n    pub fn triangle(mut self, vertices: [ImageBoxProceduralVertex; 3]) -> Self {\n        if let ImageBoxProceduralMesh::Owned(mesh) = &mut self.mesh {\n            let count = mesh.vertices.len() as u32;\n            mesh.vertices.extend(vertices);\n            mesh.triangles.push([count, count + 1, count + 2]);\n        }\n        self\n    }\n\n    pub fn quad(mut self, vertices: [ImageBoxProceduralVertex; 4]) -> Self {\n        if let ImageBoxProceduralMesh::Owned(mesh) = &mut self.mesh {\n            let count = mesh.vertices.len() as u32;\n            mesh.vertices.extend(vertices);\n            mesh.triangles.push([count, count + 1, count + 2]);\n            mesh.triangles.push([count + 2, count + 3, count]);\n        }\n        self\n    }\n\n    pub fn extend(\n        mut self,\n        vertices: impl IntoIterator<Item = ImageBoxProceduralVertex>,\n        triangles: impl IntoIterator<Item = [u32; 3]>,\n    ) -> Self {\n        if let ImageBoxProceduralMesh::Owned(mesh) = &mut self.mesh {\n            let count = mesh.vertices.len() as u32;\n            mesh.vertices.extend(vertices);\n            mesh.triangles.extend(\n                triangles\n                    .into_iter()\n                    .map(|[a, b, c]| [a + count, b + count, c + count]),\n            );\n        }\n        self\n    }\n\n    pub fn vertex_mapping(mut self, value: CoordsMappingScaling) -> Self {\n        self.vertex_mapping = value;\n        self\n    }\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub enum ImageBoxMaterial {\n    Color(ImageBoxColor),\n    Image(ImageBoxImage),\n    Procedural(ImageBoxProcedural),\n}\n\nimpl Default for ImageBoxMaterial {\n    fn default() -> Self {\n        Self::Color(Default::default())\n    }\n}\n\n#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize)]\npub enum ImageBoxSizeValue {\n    #[default]\n    Fill,\n    Exact(Scalar),\n}\n\n#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize)]\npub struct ImageBoxAspectRatio {\n    #[serde(default)]\n    pub horizontal_alignment: Scalar,\n    #[serde(default)]\n    pub vertical_alignment: Scalar,\n    #[serde(default)]\n    pub outside: bool,\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct ImageBox {\n    #[serde(default)]\n    pub id: WidgetId,\n    #[serde(default)]\n    pub width: ImageBoxSizeValue,\n    #[serde(default)]\n    pub height: ImageBoxSizeValue,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub content_keep_aspect_ratio: Option<ImageBoxAspectRatio>,\n    #[serde(default)]\n    pub material: ImageBoxMaterial,\n    #[serde(default)]\n    pub transform: Transform,\n}\n\nimpl WidgetUnitData for ImageBox {\n    fn id(&self) -> &WidgetId {\n        &self.id\n    }\n}\n\nimpl TryFrom<ImageBoxNode> for ImageBox {\n    type Error = ();\n\n    fn try_from(node: ImageBoxNode) -> Result<Self, Self::Error> {\n        let ImageBoxNode {\n            id,\n            width,\n            height,\n            content_keep_aspect_ratio,\n            material,\n            transform,\n            ..\n        } = node;\n        Ok(Self {\n            id,\n            width,\n            height,\n            content_keep_aspect_ratio,\n            material,\n            transform,\n        })\n    }\n}\n\n#[derive(Debug, Default, Clone)]\npub struct ImageBoxNode {\n    pub id: WidgetId,\n    pub props: Props,\n    pub width: ImageBoxSizeValue,\n    pub height: ImageBoxSizeValue,\n    pub content_keep_aspect_ratio: Option<ImageBoxAspectRatio>,\n    pub material: ImageBoxMaterial,\n    pub transform: Transform,\n}\n\nimpl ImageBoxNode {\n    pub fn remap_props<F>(&mut self, mut f: F)\n    where\n        F: FnMut(Props) -> Props,\n    {\n        let props = std::mem::take(&mut self.props);\n        self.props = (f)(props);\n    }\n}\n\nimpl From<ImageBoxNode> for WidgetNode {\n    fn from(data: ImageBoxNode) -> Self {\n        Self::Unit(data.into())\n    }\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub(crate) struct ImageBoxNodePrefab {\n    #[serde(default)]\n    pub id: WidgetId,\n    #[serde(default)]\n    pub props: PrefabValue,\n    #[serde(default)]\n    pub width: ImageBoxSizeValue,\n    #[serde(default)]\n    pub height: ImageBoxSizeValue,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub content_keep_aspect_ratio: Option<ImageBoxAspectRatio>,\n    #[serde(default)]\n    pub material: ImageBoxMaterial,\n    #[serde(default)]\n    pub transform: Transform,\n}\n"
  },
  {
    "path": "crates/core/src/widget/unit/mod.rs",
    "content": "pub mod area;\npub mod content;\npub mod flex;\npub mod grid;\npub mod image;\npub mod portal;\npub mod size;\npub mod text;\n\nuse crate::{\n    props::Props,\n    widget::{\n        WidgetId,\n        node::WidgetNode,\n        unit::{\n            area::{AreaBox, AreaBoxNode, AreaBoxNodePrefab},\n            content::{ContentBox, ContentBoxNode, ContentBoxNodePrefab},\n            flex::{FlexBox, FlexBoxNode, FlexBoxNodePrefab},\n            grid::{GridBox, GridBoxNode, GridBoxNodePrefab},\n            image::{ImageBox, ImageBoxNode, ImageBoxNodePrefab},\n            portal::{PortalBox, PortalBoxNode, PortalBoxNodePrefab},\n            size::{SizeBox, SizeBoxNode, SizeBoxNodePrefab},\n            text::{TextBox, TextBoxNode, TextBoxNodePrefab},\n        },\n    },\n};\nuse serde::{Deserialize, Serialize};\nuse std::convert::TryFrom;\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct WidgetUnitInspectionNode {\n    #[serde(default)]\n    pub id: WidgetId,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"Vec::is_empty\")]\n    pub children: Vec<WidgetUnitInspectionNode>,\n}\n\npub trait WidgetUnitData {\n    fn id(&self) -> &WidgetId;\n\n    fn get_children(&self) -> Vec<&WidgetUnit> {\n        vec![]\n    }\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub enum WidgetUnit {\n    #[default]\n    None,\n    AreaBox(AreaBox),\n    PortalBox(PortalBox),\n    ContentBox(ContentBox),\n    FlexBox(FlexBox),\n    GridBox(GridBox),\n    SizeBox(SizeBox),\n    ImageBox(ImageBox),\n    TextBox(TextBox),\n}\n\nimpl WidgetUnit {\n    pub fn is_none(&self) -> bool {\n        matches!(self, Self::None)\n    }\n\n    pub fn is_some(&self) -> bool {\n        !matches!(self, Self::None)\n    }\n\n    pub fn as_data(&self) -> Option<&dyn WidgetUnitData> {\n        match self {\n            Self::None => None,\n            Self::AreaBox(v) => Some(v as &dyn WidgetUnitData),\n            Self::PortalBox(v) => Some(v as &dyn WidgetUnitData),\n            Self::ContentBox(v) => Some(v as &dyn WidgetUnitData),\n            Self::FlexBox(v) => Some(v as &dyn WidgetUnitData),\n            Self::GridBox(v) => Some(v as &dyn WidgetUnitData),\n            Self::SizeBox(v) => Some(v as &dyn WidgetUnitData),\n            Self::ImageBox(v) => Some(v as &dyn WidgetUnitData),\n            Self::TextBox(v) => Some(v as &dyn WidgetUnitData),\n        }\n    }\n\n    pub fn inspect(&self) -> Option<WidgetUnitInspectionNode> {\n        self.as_data().map(|data| WidgetUnitInspectionNode {\n            id: data.id().to_owned(),\n            children: data\n                .get_children()\n                .into_iter()\n                .filter_map(|child| child.inspect())\n                .collect::<Vec<_>>(),\n        })\n    }\n}\n\nimpl TryFrom<WidgetUnitNode> for WidgetUnit {\n    type Error = ();\n\n    fn try_from(node: WidgetUnitNode) -> Result<Self, Self::Error> {\n        match node {\n            WidgetUnitNode::None => Ok(Self::None),\n            WidgetUnitNode::AreaBox(n) => Ok(WidgetUnit::AreaBox(AreaBox::try_from(n)?)),\n            WidgetUnitNode::PortalBox(n) => Ok(WidgetUnit::PortalBox(PortalBox::try_from(n)?)),\n            WidgetUnitNode::ContentBox(n) => Ok(WidgetUnit::ContentBox(ContentBox::try_from(n)?)),\n            WidgetUnitNode::FlexBox(n) => Ok(WidgetUnit::FlexBox(FlexBox::try_from(n)?)),\n            WidgetUnitNode::GridBox(n) => Ok(WidgetUnit::GridBox(GridBox::try_from(n)?)),\n            WidgetUnitNode::SizeBox(n) => Ok(WidgetUnit::SizeBox(SizeBox::try_from(n)?)),\n            WidgetUnitNode::ImageBox(n) => Ok(WidgetUnit::ImageBox(ImageBox::try_from(n)?)),\n            WidgetUnitNode::TextBox(n) => Ok(WidgetUnit::TextBox(TextBox::try_from(n)?)),\n        }\n    }\n}\n\nimpl TryFrom<WidgetNode> for WidgetUnit {\n    type Error = ();\n\n    fn try_from(node: WidgetNode) -> Result<Self, Self::Error> {\n        match node {\n            WidgetNode::None | WidgetNode::Tuple(_) => Ok(Self::None),\n            WidgetNode::Component(_) => Err(()),\n            WidgetNode::Unit(u) => Self::try_from(u),\n        }\n    }\n}\n\n#[derive(Debug, Default, Clone)]\npub enum WidgetUnitNode {\n    #[default]\n    None,\n    AreaBox(AreaBoxNode),\n    PortalBox(PortalBoxNode),\n    ContentBox(ContentBoxNode),\n    FlexBox(FlexBoxNode),\n    GridBox(GridBoxNode),\n    SizeBox(SizeBoxNode),\n    ImageBox(ImageBoxNode),\n    TextBox(TextBoxNode),\n}\n\nimpl WidgetUnitNode {\n    pub fn is_none(&self) -> bool {\n        matches!(self, Self::None)\n    }\n\n    pub fn is_some(&self) -> bool {\n        !matches!(self, Self::None)\n    }\n\n    pub fn props(&self) -> Option<&Props> {\n        match self {\n            Self::None | Self::AreaBox(_) | Self::PortalBox(_) => None,\n            Self::ContentBox(v) => Some(&v.props),\n            Self::FlexBox(v) => Some(&v.props),\n            Self::GridBox(v) => Some(&v.props),\n            Self::SizeBox(v) => Some(&v.props),\n            Self::ImageBox(v) => Some(&v.props),\n            Self::TextBox(v) => Some(&v.props),\n        }\n    }\n\n    pub fn props_mut(&mut self) -> Option<&mut Props> {\n        match self {\n            Self::None | Self::AreaBox(_) | Self::PortalBox(_) => None,\n            Self::ContentBox(v) => Some(&mut v.props),\n            Self::FlexBox(v) => Some(&mut v.props),\n            Self::GridBox(v) => Some(&mut v.props),\n            Self::SizeBox(v) => Some(&mut v.props),\n            Self::ImageBox(v) => Some(&mut v.props),\n            Self::TextBox(v) => Some(&mut v.props),\n        }\n    }\n\n    pub fn remap_props<F>(&mut self, f: F)\n    where\n        F: FnMut(Props) -> Props,\n    {\n        match self {\n            Self::None | Self::AreaBox(_) | Self::PortalBox(_) => {}\n            Self::ContentBox(v) => v.remap_props(f),\n            Self::FlexBox(v) => v.remap_props(f),\n            Self::GridBox(v) => v.remap_props(f),\n            Self::SizeBox(v) => v.remap_props(f),\n            Self::ImageBox(v) => v.remap_props(f),\n            Self::TextBox(v) => v.remap_props(f),\n        }\n    }\n}\n\nimpl TryFrom<WidgetNode> for WidgetUnitNode {\n    type Error = ();\n\n    fn try_from(node: WidgetNode) -> Result<Self, Self::Error> {\n        if let WidgetNode::Unit(v) = node {\n            Ok(v)\n        } else {\n            Err(())\n        }\n    }\n}\n\nimpl From<()> for WidgetUnitNode {\n    fn from(_: ()) -> Self {\n        Self::None\n    }\n}\n\nmacro_rules! implement_from_unit {\n    { $( $type_name:ident => $variant_name:ident ),+ $(,)? } => {\n        $(\n            impl From<$type_name> for WidgetUnitNode {\n                fn from(unit: $type_name) -> Self {\n                    Self::$variant_name(unit)\n                }\n            }\n        )+\n    };\n}\n\nimplement_from_unit! {\n    AreaBoxNode => AreaBox,\n    PortalBoxNode => PortalBox,\n    ContentBoxNode => ContentBox,\n    FlexBoxNode => FlexBox,\n    GridBoxNode => GridBox,\n    SizeBoxNode => SizeBox,\n    ImageBoxNode => ImageBox,\n    TextBoxNode => TextBox,\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub(crate) enum WidgetUnitNodePrefab {\n    #[default]\n    None,\n    AreaBox(AreaBoxNodePrefab),\n    PortalBox(PortalBoxNodePrefab),\n    ContentBox(ContentBoxNodePrefab),\n    FlexBox(FlexBoxNodePrefab),\n    GridBox(GridBoxNodePrefab),\n    SizeBox(SizeBoxNodePrefab),\n    ImageBox(ImageBoxNodePrefab),\n    TextBox(TextBoxNodePrefab),\n}\n"
  },
  {
    "path": "crates/core/src/widget/unit/portal.rs",
    "content": "use crate::widget::{\n    WidgetId,\n    node::{WidgetNode, WidgetNodePrefab},\n    unit::{\n        WidgetUnit, WidgetUnitData,\n        content::{ContentBoxItem, ContentBoxItemNode, ContentBoxItemNodePrefab},\n        flex::{FlexBoxItem, FlexBoxItemNode, FlexBoxItemNodePrefab},\n        grid::{GridBoxItem, GridBoxItemNode, GridBoxItemNodePrefab},\n    },\n};\nuse serde::{Deserialize, Serialize};\nuse std::convert::TryFrom;\n\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub enum PortalBoxSlot {\n    Slot(WidgetUnit),\n    ContentItem(ContentBoxItem),\n    FlexItem(FlexBoxItem),\n    GridItem(GridBoxItem),\n}\n\nimpl Default for PortalBoxSlot {\n    fn default() -> Self {\n        Self::Slot(Default::default())\n    }\n}\n\nimpl TryFrom<PortalBoxSlotNode> for PortalBoxSlot {\n    type Error = ();\n\n    fn try_from(node: PortalBoxSlotNode) -> Result<Self, Self::Error> {\n        Ok(match node {\n            PortalBoxSlotNode::Slot(node) => PortalBoxSlot::Slot(WidgetUnit::try_from(node)?),\n            PortalBoxSlotNode::ContentItem(item) => {\n                PortalBoxSlot::ContentItem(ContentBoxItem::try_from(item)?)\n            }\n            PortalBoxSlotNode::FlexItem(item) => {\n                PortalBoxSlot::FlexItem(FlexBoxItem::try_from(item)?)\n            }\n            PortalBoxSlotNode::GridItem(item) => {\n                PortalBoxSlot::GridItem(GridBoxItem::try_from(item)?)\n            }\n        })\n    }\n}\n\n#[derive(Debug, Clone)]\npub enum PortalBoxSlotNode {\n    Slot(WidgetNode),\n    ContentItem(ContentBoxItemNode),\n    FlexItem(FlexBoxItemNode),\n    GridItem(GridBoxItemNode),\n}\n\nimpl Default for PortalBoxSlotNode {\n    fn default() -> Self {\n        Self::Slot(Default::default())\n    }\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct PortalBox {\n    #[serde(default)]\n    pub id: WidgetId,\n    #[serde(default)]\n    pub slot: Box<PortalBoxSlot>,\n    #[serde(default)]\n    pub owner: WidgetId,\n}\n\nimpl WidgetUnitData for PortalBox {\n    fn id(&self) -> &WidgetId {\n        &self.id\n    }\n\n    fn get_children(&self) -> Vec<&WidgetUnit> {\n        vec![match &*self.slot {\n            PortalBoxSlot::Slot(b) => b,\n            PortalBoxSlot::ContentItem(b) => &b.slot,\n            PortalBoxSlot::FlexItem(b) => &b.slot,\n            PortalBoxSlot::GridItem(b) => &b.slot,\n        }]\n    }\n}\n\nimpl TryFrom<PortalBoxNode> for PortalBox {\n    type Error = ();\n\n    fn try_from(node: PortalBoxNode) -> Result<Self, Self::Error> {\n        let PortalBoxNode { id, slot, owner } = node;\n        Ok(Self {\n            id,\n            slot: Box::new(PortalBoxSlot::try_from(*slot)?),\n            owner,\n        })\n    }\n}\n\n#[derive(Debug, Default, Clone)]\npub struct PortalBoxNode {\n    pub id: WidgetId,\n    pub slot: Box<PortalBoxSlotNode>,\n    pub owner: WidgetId,\n}\n\nimpl From<PortalBoxNode> for WidgetNode {\n    fn from(data: PortalBoxNode) -> Self {\n        Self::Unit(data.into())\n    }\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub(crate) struct PortalBoxNodePrefab {\n    #[serde(default)]\n    pub id: WidgetId,\n    #[serde(default)]\n    pub slot: Box<PortalBoxSlotNodePrefab>,\n    #[serde(default)]\n    pub owner: WidgetId,\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub(crate) enum PortalBoxSlotNodePrefab {\n    Slot(#[serde(default)] WidgetNodePrefab),\n    ContentItem(#[serde(default)] ContentBoxItemNodePrefab),\n    FlexItem(#[serde(default)] FlexBoxItemNodePrefab),\n    GridItem(#[serde(default)] GridBoxItemNodePrefab),\n}\n\nimpl Default for PortalBoxSlotNodePrefab {\n    fn default() -> Self {\n        Self::Slot(Default::default())\n    }\n}\n"
  },
  {
    "path": "crates/core/src/widget/unit/size.rs",
    "content": "use crate::{\n    PrefabValue, Scalar,\n    props::Props,\n    widget::{\n        WidgetId,\n        node::{WidgetNode, WidgetNodePrefab},\n        unit::{WidgetUnit, WidgetUnitData},\n        utils::{Rect, Transform},\n    },\n};\nuse serde::{Deserialize, Serialize};\nuse std::convert::TryFrom;\n\n#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize)]\npub enum SizeBoxSizeValue {\n    #[default]\n    Content,\n    Fill,\n    Exact(Scalar),\n}\n\n#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize)]\npub enum SizeBoxAspectRatio {\n    #[default]\n    None,\n    WidthOfHeight(Scalar),\n    HeightOfWidth(Scalar),\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct SizeBox {\n    #[serde(default)]\n    pub id: WidgetId,\n    #[serde(default)]\n    pub slot: Box<WidgetUnit>,\n    #[serde(default)]\n    pub width: SizeBoxSizeValue,\n    #[serde(default)]\n    pub height: SizeBoxSizeValue,\n    #[serde(default)]\n    pub margin: Rect,\n    #[serde(default)]\n    pub keep_aspect_ratio: SizeBoxAspectRatio,\n    #[serde(default)]\n    pub transform: Transform,\n}\n\nimpl WidgetUnitData for SizeBox {\n    fn id(&self) -> &WidgetId {\n        &self.id\n    }\n\n    fn get_children(&self) -> Vec<&WidgetUnit> {\n        vec![&self.slot]\n    }\n}\n\nimpl TryFrom<SizeBoxNode> for SizeBox {\n    type Error = ();\n\n    fn try_from(node: SizeBoxNode) -> Result<Self, Self::Error> {\n        let SizeBoxNode {\n            id,\n            slot,\n            width,\n            height,\n            margin,\n            keep_aspect_ratio,\n            transform,\n            ..\n        } = node;\n        Ok(Self {\n            id,\n            slot: Box::new(WidgetUnit::try_from(*slot)?),\n            width,\n            height,\n            margin,\n            keep_aspect_ratio,\n            transform,\n        })\n    }\n}\n\n#[derive(Debug, Default, Clone)]\npub struct SizeBoxNode {\n    pub id: WidgetId,\n    pub props: Props,\n    pub slot: Box<WidgetNode>,\n    pub width: SizeBoxSizeValue,\n    pub height: SizeBoxSizeValue,\n    pub margin: Rect,\n    pub keep_aspect_ratio: SizeBoxAspectRatio,\n    pub transform: Transform,\n}\n\nimpl SizeBoxNode {\n    pub fn remap_props<F>(&mut self, mut f: F)\n    where\n        F: FnMut(Props) -> Props,\n    {\n        let props = std::mem::take(&mut self.props);\n        self.props = (f)(props);\n    }\n}\n\nimpl From<SizeBoxNode> for WidgetNode {\n    fn from(data: SizeBoxNode) -> Self {\n        Self::Unit(data.into())\n    }\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub(crate) struct SizeBoxNodePrefab {\n    #[serde(default)]\n    pub id: WidgetId,\n    #[serde(default)]\n    pub props: PrefabValue,\n    #[serde(default)]\n    pub slot: Box<WidgetNodePrefab>,\n    #[serde(default)]\n    pub width: SizeBoxSizeValue,\n    #[serde(default)]\n    pub height: SizeBoxSizeValue,\n    #[serde(default)]\n    pub keep_aspect_ratio: SizeBoxAspectRatio,\n    #[serde(default)]\n    pub margin: Rect,\n    #[serde(default)]\n    pub transform: Transform,\n}\n"
  },
  {
    "path": "crates/core/src/widget/unit/text.rs",
    "content": "use crate::{\n    PrefabValue, Scalar,\n    props::Props,\n    widget::{\n        WidgetId,\n        node::WidgetNode,\n        unit::WidgetUnitData,\n        utils::{Color, Transform},\n    },\n};\nuse serde::{Deserialize, Serialize};\nuse std::convert::TryFrom;\n\n#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]\npub enum TextBoxHorizontalAlign {\n    #[default]\n    Left,\n    Center,\n    Right,\n}\n\n#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]\npub enum TextBoxVerticalAlign {\n    #[default]\n    Top,\n    Middle,\n    Bottom,\n}\n\n#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]\npub enum TextBoxDirection {\n    #[default]\n    HorizontalLeftToRight,\n    HorizontalRightToLeft,\n    VerticalTopToBottom,\n    VerticalBottomToTop,\n}\n\nimpl TextBoxDirection {\n    pub fn is_horizontal(&self) -> bool {\n        *self == Self::HorizontalLeftToRight || *self == Self::HorizontalRightToLeft\n    }\n\n    pub fn is_vertical(&self) -> bool {\n        *self == Self::VerticalTopToBottom || *self == Self::VerticalBottomToTop\n    }\n\n    pub fn is_order_ascending(&self) -> bool {\n        *self == Self::HorizontalLeftToRight || *self == Self::VerticalTopToBottom\n    }\n\n    pub fn is_order_descending(&self) -> bool {\n        *self == Self::HorizontalRightToLeft || *self == Self::VerticalBottomToTop\n    }\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct TextBoxFont {\n    #[serde(default)]\n    pub name: String,\n    #[serde(default)]\n    pub size: Scalar,\n}\n\n#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize)]\npub enum TextBoxSizeValue {\n    Content,\n    #[default]\n    Fill,\n    Exact(Scalar),\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct TextBox {\n    #[serde(default)]\n    pub id: WidgetId,\n    #[serde(default)]\n    pub text: String,\n    #[serde(default)]\n    pub width: TextBoxSizeValue,\n    #[serde(default)]\n    pub height: TextBoxSizeValue,\n    #[serde(default)]\n    pub horizontal_align: TextBoxHorizontalAlign,\n    #[serde(default)]\n    pub vertical_align: TextBoxVerticalAlign,\n    #[serde(default)]\n    pub direction: TextBoxDirection,\n    #[serde(default)]\n    pub font: TextBoxFont,\n    #[serde(default)]\n    pub color: Color,\n    #[serde(default)]\n    pub transform: Transform,\n}\n\nimpl WidgetUnitData for TextBox {\n    fn id(&self) -> &WidgetId {\n        &self.id\n    }\n}\n\nimpl TryFrom<TextBoxNode> for TextBox {\n    type Error = ();\n\n    fn try_from(node: TextBoxNode) -> Result<Self, Self::Error> {\n        let TextBoxNode {\n            id,\n            text,\n            width,\n            height,\n            horizontal_align,\n            vertical_align,\n            direction,\n            font,\n            color,\n            transform,\n            ..\n        } = node;\n        Ok(Self {\n            id,\n            text,\n            width,\n            height,\n            horizontal_align,\n            vertical_align,\n            direction,\n            font,\n            color,\n            transform,\n        })\n    }\n}\n\n#[derive(Debug, Default, Clone)]\npub struct TextBoxNode {\n    pub id: WidgetId,\n    pub props: Props,\n    pub text: String,\n    pub width: TextBoxSizeValue,\n    pub height: TextBoxSizeValue,\n    pub horizontal_align: TextBoxHorizontalAlign,\n    pub vertical_align: TextBoxVerticalAlign,\n    pub direction: TextBoxDirection,\n    pub font: TextBoxFont,\n    pub color: Color,\n    pub transform: Transform,\n}\n\nimpl TextBoxNode {\n    pub fn remap_props<F>(&mut self, mut f: F)\n    where\n        F: FnMut(Props) -> Props,\n    {\n        let props = std::mem::take(&mut self.props);\n        self.props = (f)(props);\n    }\n}\n\nimpl From<TextBoxNode> for WidgetNode {\n    fn from(data: TextBoxNode) -> Self {\n        Self::Unit(data.into())\n    }\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub(crate) struct TextBoxNodePrefab {\n    #[serde(default)]\n    pub id: WidgetId,\n    #[serde(default)]\n    pub props: PrefabValue,\n    #[serde(default)]\n    pub text: String,\n    #[serde(default)]\n    pub width: TextBoxSizeValue,\n    #[serde(default)]\n    pub height: TextBoxSizeValue,\n    #[serde(default)]\n    pub horizontal_align: TextBoxHorizontalAlign,\n    #[serde(default)]\n    pub vertical_align: TextBoxVerticalAlign,\n    #[serde(default)]\n    pub direction: TextBoxDirection,\n    #[serde(default)]\n    pub font: TextBoxFont,\n    #[serde(default)]\n    pub color: Color,\n    #[serde(default)]\n    pub transform: Transform,\n}\n"
  },
  {
    "path": "crates/core/src/widget/utils.rs",
    "content": "use crate::{Integer, PropsData, Scalar};\nuse serde::{Deserialize, Serialize};\n\n#[repr(C)]\n#[derive(PropsData, Debug, Default, Copy, Clone, PartialEq, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct Vec2 {\n    #[serde(default)]\n    pub x: Scalar,\n    #[serde(default)]\n    pub y: Scalar,\n}\n\nimpl From<Scalar> for Vec2 {\n    fn from(v: Scalar) -> Self {\n        Self { x: v, y: v }\n    }\n}\n\nimpl From<(Scalar, Scalar)> for Vec2 {\n    fn from((x, y): (Scalar, Scalar)) -> Self {\n        Self { x, y }\n    }\n}\n\nimpl From<[Scalar; 2]> for Vec2 {\n    fn from([x, y]: [Scalar; 2]) -> Self {\n        Self { x, y }\n    }\n}\n\n#[repr(C)]\n#[derive(PropsData, Debug, Default, Copy, Clone, PartialEq, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct IntVec2 {\n    #[serde(default)]\n    pub x: Integer,\n    #[serde(default)]\n    pub y: Integer,\n}\n\nimpl From<Integer> for IntVec2 {\n    fn from(v: Integer) -> Self {\n        Self { x: v, y: v }\n    }\n}\n\nimpl From<(Integer, Integer)> for IntVec2 {\n    fn from((x, y): (Integer, Integer)) -> Self {\n        Self { x, y }\n    }\n}\n\nimpl From<[Integer; 2]> for IntVec2 {\n    fn from([x, y]: [Integer; 2]) -> Self {\n        Self { x, y }\n    }\n}\n\n#[repr(C)]\n#[derive(PropsData, Debug, Default, Copy, Clone, PartialEq, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct Rect {\n    #[serde(default)]\n    pub left: Scalar,\n    #[serde(default)]\n    pub right: Scalar,\n    #[serde(default)]\n    pub top: Scalar,\n    #[serde(default)]\n    pub bottom: Scalar,\n}\n\nimpl From<Scalar> for Rect {\n    fn from(v: Scalar) -> Self {\n        Self {\n            left: v,\n            right: v,\n            top: v,\n            bottom: v,\n        }\n    }\n}\n\nimpl From<(Scalar, Scalar)> for Rect {\n    fn from((w, h): (Scalar, Scalar)) -> Self {\n        Self {\n            left: 0.0,\n            right: w,\n            top: 0.0,\n            bottom: h,\n        }\n    }\n}\n\nimpl From<[Scalar; 2]> for Rect {\n    fn from([w, h]: [Scalar; 2]) -> Self {\n        Self {\n            left: 0.0,\n            right: w,\n            top: 0.0,\n            bottom: h,\n        }\n    }\n}\n\nimpl From<(Scalar, Scalar, Scalar, Scalar)> for Rect {\n    fn from((left, right, top, bottom): (Scalar, Scalar, Scalar, Scalar)) -> Self {\n        Self {\n            left,\n            right,\n            top,\n            bottom,\n        }\n    }\n}\n\nimpl From<[Scalar; 4]> for Rect {\n    fn from([left, right, top, bottom]: [Scalar; 4]) -> Self {\n        Self {\n            left,\n            right,\n            top,\n            bottom,\n        }\n    }\n}\n\nimpl Rect {\n    #[inline]\n    pub fn width(&self) -> Scalar {\n        self.right - self.left\n    }\n\n    #[inline]\n    pub fn height(&self) -> Scalar {\n        self.bottom - self.top\n    }\n\n    #[inline]\n    pub fn size(&self) -> Vec2 {\n        Vec2 {\n            x: self.width(),\n            y: self.height(),\n        }\n    }\n}\n\n#[repr(C)]\n#[derive(PropsData, Debug, Default, Copy, Clone, PartialEq, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct IntRect {\n    #[serde(default)]\n    pub left: Integer,\n    #[serde(default)]\n    pub right: Integer,\n    #[serde(default)]\n    pub top: Integer,\n    #[serde(default)]\n    pub bottom: Integer,\n}\n\nimpl IntRect {\n    #[inline]\n    pub fn width(&self) -> Integer {\n        self.right - self.left\n    }\n\n    #[inline]\n    pub fn height(&self) -> Integer {\n        self.bottom - self.top\n    }\n\n    #[inline]\n    pub fn size(&self) -> IntVec2 {\n        IntVec2 {\n            x: self.width(),\n            y: self.height(),\n        }\n    }\n}\n\nimpl From<Integer> for IntRect {\n    fn from(v: Integer) -> Self {\n        Self {\n            left: v,\n            right: v,\n            top: v,\n            bottom: v,\n        }\n    }\n}\n\nimpl From<(Integer, Integer)> for IntRect {\n    fn from((w, h): (Integer, Integer)) -> Self {\n        Self {\n            left: 0,\n            right: w,\n            top: 0,\n            bottom: h,\n        }\n    }\n}\n\n#[repr(C)]\n#[derive(PropsData, Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct Color {\n    #[serde(default)]\n    pub r: Scalar,\n    #[serde(default)]\n    pub g: Scalar,\n    #[serde(default)]\n    pub b: Scalar,\n    #[serde(default)]\n    pub a: Scalar,\n}\n\nimpl Default for Color {\n    fn default() -> Self {\n        Self {\n            r: 1.0,\n            g: 1.0,\n            b: 1.0,\n            a: 1.0,\n        }\n    }\n}\n\nimpl Color {\n    pub fn transparent() -> Self {\n        Self {\n            r: 0.0,\n            g: 0.0,\n            b: 0.0,\n            a: 0.0,\n        }\n    }\n}\n\n#[derive(PropsData, Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]\n#[props_data(crate::props::PropsData)]\n#[prefab(crate::Prefab)]\npub struct Transform {\n    /// Rectangle center of mass. Values in range: <0;1>\n    #[serde(default)]\n    pub pivot: Vec2,\n    /// Translation in rectangle fraction units. Values in range: <0;1>\n    #[serde(default)]\n    pub align: Vec2,\n    /// Translation in regular units.\n    #[serde(default)]\n    pub translation: Vec2,\n    /// Rotation in radian angle units.\n    #[serde(default)]\n    pub rotation: Scalar,\n    /// Scale in regular units.\n    #[serde(default)]\n    pub scale: Vec2,\n    /// Skewing in radian angle units.\n    /// {angle X, angle Y}\n    #[serde(default)]\n    pub skew: Vec2,\n}\n\nimpl Default for Transform {\n    fn default() -> Self {\n        Self {\n            pivot: Default::default(),\n            align: Default::default(),\n            translation: Default::default(),\n            rotation: Default::default(),\n            scale: Self::default_scale(),\n            skew: Default::default(),\n        }\n    }\n}\n\nimpl Transform {\n    fn default_scale() -> Vec2 {\n        Vec2 { x: 1.0, y: 1.0 }\n    }\n}\n\n#[inline]\npub fn lerp(from: Scalar, to: Scalar, factor: Scalar) -> Scalar {\n    from + (to - from) * factor\n}\n\n#[inline]\npub fn lerp_clamped(from: Scalar, to: Scalar, factor: Scalar) -> Scalar {\n    lerp(from, to, factor.clamp(0.0, 1.0))\n}\n"
  },
  {
    "path": "crates/derive/Cargo.toml",
    "content": "[package]\nname = \"raui-derive\"\nversion = \"0.70.17\"\nauthors = [\"Patryk 'PsichiX' Budzynski <psichix@gmail.com>\"]\nedition = \"2024\"\ndescription = \"Macros for Renderer Agnostic User Interface\"\nreadme = \"../../README.md\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/RAUI-labs/raui\"\nkeywords = [\"renderer\", \"agnostic\", \"ui\", \"interface\", \"gamedev\"]\ncategories = [\"gui\", \"rendering::graphics-api\"]\n\n[lib]\nproc-macro = true\n\n[dependencies]\nquote = \"1.0\"\nsyn = { version = \"1.0\", features = [\"extra-traits\", \"full\"] }\n"
  },
  {
    "path": "crates/derive/src/lib.rs",
    "content": "extern crate proc_macro;\n\nuse proc_macro::TokenStream;\nuse quote::quote;\nuse syn::{\n    DeriveInput, FnArg, Ident, ItemFn, Pat, PatIdent, Path, Result, Token, Type, TypePath,\n    TypeReference,\n    parse::{Parse, ParseStream},\n    parse_macro_input, parse_str,\n    punctuated::Punctuated,\n};\n\n#[derive(Debug, Clone)]\nstruct IdentList {\n    values: Punctuated<Ident, Token![,]>,\n}\n\nimpl Parse for IdentList {\n    fn parse(input: ParseStream) -> Result<Self> {\n        Ok(Self {\n            values: input.parse_terminated(Ident::parse)?,\n        })\n    }\n}\n\nfn unpack_context(ty: &Type, pat: &Pat) -> Option<Ident> {\n    match ty {\n        Type::Path(TypePath { path, .. }) => {\n            if let Some(segment) = path.segments.iter().next_back()\n                && segment.ident == \"WidgetContext\"\n                && let Pat::Ident(PatIdent { ident, .. }) = pat\n            {\n                return Some(ident.to_owned());\n            }\n        }\n        Type::Reference(TypeReference { elem, .. }) => {\n            return unpack_context(elem, pat);\n        }\n        _ => {}\n    }\n    None\n}\n\nfn is_arg_context(arg: &FnArg) -> Option<Ident> {\n    if let FnArg::Typed(pat) = arg {\n        unpack_context(&pat.ty, &pat.pat)\n    } else {\n        None\n    }\n}\n\n// The links won't be broken when built in the context of the `raui` crate\n/// An attribute macro that allows you to add hooks that will execute before your component body\n///\n/// > **See Also:** [`macro@post_hooks`] for an alternative that runs _after_ your component body\n///\n/// Hooks allow you to create reusable logic that can be applied to multiple components.\n#[proc_macro_attribute]\npub fn pre_hooks(attr: TokenStream, input: TokenStream) -> TokenStream {\n    let ItemFn {\n        attrs,\n        vis,\n        sig,\n        block,\n    } = parse_macro_input!(input as ItemFn);\n    let context = sig\n        .inputs\n        .iter()\n        .find_map(is_arg_context)\n        .unwrap_or_else(|| panic!(\"Could not find function context argument!\"));\n    let list = parse_macro_input!(attr as IdentList);\n    let hooks = list\n        .values\n        .into_iter()\n        .map(|v| quote! { #context.use_hook(#v); });\n\n    let tokens = quote! {\n        #(#attrs)*\n        #vis #sig {\n            #({#hooks})*\n            #block\n        }\n    };\n    tokens.into()\n}\n\n/// Allows you to execute re-usable logic after your component body\n///\n/// See [`macro@pre_hooks`]\n#[proc_macro_attribute]\npub fn post_hooks(attr: TokenStream, input: TokenStream) -> TokenStream {\n    let ItemFn {\n        attrs,\n        vis,\n        sig,\n        block,\n    } = parse_macro_input!(input as ItemFn);\n    let context = sig\n        .inputs\n        .iter()\n        .find_map(is_arg_context)\n        .unwrap_or_else(|| panic!(\"Could not find function context argument!\"));\n    let list = parse_macro_input!(attr as IdentList);\n    let hooks = list\n        .values\n        .into_iter()\n        .map(|v| quote! { #context.use_hook(#v); });\n\n    let tokens = quote! {\n        #(#attrs)*\n        #vis #sig {\n            let result = {\n                #block\n            };\n            #({#hooks})*\n            result\n        }\n    };\n    tokens.into()\n}\n\n// The links won't be broken when built in the context of the `raui` crate\n/// Derive macro for the [`PropsData`][raui_core::props::PropsData] trait\n///\n/// # Example\n///\n/// ```ignore\n/// #[derive(PropsData, Debug, Default, Copy, Clone, Serialize, Deserialize)]\n/// #[props_data(crate::props::PropsData)]\n/// #[prefab(crate::Prefab)]\n/// pub struct ButtonProps {\n///     #[serde(default)]\n///     pub selected: bool,\n///     #[serde(default)]\n///     pub trigger: bool,\n///     #[serde(default)]\n///     pub context: bool,\n///     #[serde(default)]\n///     pub pointer: Vec2,\n/// }\n/// ```\n#[proc_macro_derive(PropsData, attributes(remote, props_data, prefab))]\npub fn derive_props(input: TokenStream) -> TokenStream {\n    let DeriveInput { ident, attrs, .. } = parse_macro_input!(input as DeriveInput);\n\n    let mut path = Path::from(ident);\n    let mut props_data = parse_str::<Path>(\"PropsData\").unwrap();\n    let mut prefab = parse_str::<Path>(\"Prefab\").unwrap();\n    for attr in attrs {\n        if let Some(ident) = attr.path.get_ident() {\n            if ident == \"remote\" {\n                path = attr.parse_args::<Path>().unwrap();\n            } else if ident == \"props_data\" {\n                props_data = attr.parse_args::<Path>().unwrap();\n            } else if ident == \"prefab\" {\n                prefab = attr.parse_args::<Path>().unwrap();\n            }\n        }\n    }\n\n    let tokens = quote! {\n        impl #props_data for #path\n        where\n            Self: Clone,\n        {\n            fn clone_props(&self) -> Box<dyn #props_data> {\n                Box::new(self.clone())\n            }\n\n            fn as_any(&self) -> &dyn std::any::Any {\n                self\n            }\n        }\n\n        impl #prefab for #path {}\n    };\n    tokens.into()\n}\n\n// The links won't be broken when built in the context of the `raui` crate\n/// Derive macro for the [`MessageData`][raui_core::messenger::MessageData] trait\n///\n/// # Example\n///\n/// ```ignore\n/// #[derive(MessageData, Debug, Clone)]\n/// pub enum AppMessage {\n///     ShowPopup(usize),\n///     ClosePopup,\n/// }\n/// ```\n#[proc_macro_derive(MessageData, attributes(remote, message_data))]\npub fn derive_message(input: TokenStream) -> TokenStream {\n    let DeriveInput { ident, attrs, .. } = parse_macro_input!(input as DeriveInput);\n\n    let mut path = Path::from(ident);\n    let mut message_data = parse_str::<Path>(\"MessageData\").unwrap();\n    for attr in attrs {\n        if let Some(ident) = attr.path.get_ident() {\n            if ident == \"remote\" {\n                path = attr.parse_args::<Path>().unwrap();\n            } else if ident == \"message_data\" {\n                message_data = attr.parse_args::<Path>().unwrap();\n            }\n        }\n    }\n\n    let tokens = quote! {\n        impl #message_data for #path\n        where\n            Self: Clone,\n        {\n            fn clone_message(&self) -> Box<dyn #message_data> {\n                Box::new(self.clone())\n            }\n\n            fn as_any(&self) -> &dyn std::any::Any {\n                self\n            }\n        }\n    };\n    tokens.into()\n}\n"
  },
  {
    "path": "crates/immediate/Cargo.toml",
    "content": "[package]\nname = \"raui-immediate\"\nversion = \"0.70.17\"\nauthors = [\"Patryk 'PsichiX' Budzynski <psichix@gmail.com>\"]\nedition = \"2024\"\ndescription = \"RAUI immediate mode UI layer\"\nreadme = \"../../README.md\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/RAUI-labs/raui\"\nkeywords = [\"renderer\", \"agnostic\", \"ui\", \"interface\", \"gamedev\"]\ncategories = [\"gui\", \"rendering::graphics-api\"]\n\n[dependencies]\nraui-core = { path = \"../core\", version = \"0.70\" }\nserde = { version = \"1\", features = [\"derive\"] }\n"
  },
  {
    "path": "crates/immediate/src/lib.rs",
    "content": "use internal::immediate_effects_box;\nuse raui_core::{\n    DynamicManaged, DynamicManagedLazy, Lifetime, ManagedLazy, Prefab, PropsData, TypeHash,\n    make_widget,\n    props::{Props, PropsData},\n    widget::{\n        WidgetRef, component::WidgetComponent, context::WidgetContext, node::WidgetNode,\n        unit::WidgetUnitNode,\n    },\n};\nuse serde::{Deserialize, Serialize};\nuse std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc};\n\nthread_local! {\n    pub(crate) static STACK: RefCell<Vec<Vec<WidgetNode>>> = Default::default();\n    pub(crate) static STATES: RefCell<Option<Rc<RefCell<ImmediateStates>>>> = Default::default();\n    pub(crate) static ACCESS_POINTS: RefCell<Option<Rc<RefCell<ImmediateAccessPoints>>>> = Default::default();\n    pub(crate) static PROPS_STACK: RefCell<Option<Rc<RefCell<Vec<Props>>>>> = Default::default();\n}\n\n#[derive(Default)]\npub struct ImmediateContext {\n    states: Rc<RefCell<ImmediateStates>>,\n    access_points: Rc<RefCell<ImmediateAccessPoints>>,\n    props_stack: Rc<RefCell<Vec<Props>>>,\n}\n\nimpl ImmediateContext {\n    pub fn activate(context: &Self) {\n        STATES.with(|states| {\n            context.states.borrow_mut().reset();\n            *states.borrow_mut() = Some(context.states.clone());\n        });\n        ACCESS_POINTS.with(|access_points| {\n            *access_points.borrow_mut() = Some(context.access_points.clone());\n        });\n        PROPS_STACK.with(|props_stack| {\n            *props_stack.borrow_mut() = Some(context.props_stack.clone());\n        });\n    }\n\n    pub fn deactivate() {\n        STATES.with(|states| {\n            *states.borrow_mut() = None;\n        });\n        ACCESS_POINTS.with(|access_points| {\n            if let Some(access_points) = access_points.borrow_mut().as_mut() {\n                access_points.borrow_mut().reset();\n            }\n            *access_points.borrow_mut() = None;\n        });\n        PROPS_STACK.with(|props_stack| {\n            if let Some(props_stack) = props_stack.borrow_mut().as_mut() {\n                props_stack.borrow_mut().clear();\n            }\n            *props_stack.borrow_mut() = None;\n        });\n    }\n}\n\n#[derive(Default)]\nstruct ImmediateStates {\n    data: Vec<DynamicManaged>,\n    position: usize,\n}\n\nimpl ImmediateStates {\n    fn reset(&mut self) {\n        self.data.truncate(self.position);\n        self.position = 0;\n    }\n\n    fn alloc<T>(&mut self, mut init: impl FnMut() -> T) -> ManagedLazy<T> {\n        let index = self.position;\n        self.position += 1;\n        if let Some(managed) = self.data.get_mut(index) {\n            if managed.type_hash() != &TypeHash::of::<T>() {\n                *managed = DynamicManaged::new(init()).ok().unwrap();\n            }\n        } else {\n            self.data.push(DynamicManaged::new(init()).ok().unwrap());\n        }\n        self.data\n            .get(index)\n            .unwrap()\n            .lazy()\n            .into_typed()\n            .ok()\n            .unwrap()\n    }\n}\n\n#[derive(Default)]\nstruct ImmediateAccessPoints {\n    data: HashMap<String, DynamicManagedLazy>,\n}\n\nimpl ImmediateAccessPoints {\n    fn register<T>(&mut self, id: impl ToString, data: &mut T) -> Lifetime {\n        let result = Lifetime::default();\n        self.data\n            .insert(id.to_string(), DynamicManagedLazy::new(data, result.lazy()));\n        result\n    }\n\n    fn reset(&mut self) {\n        self.data.clear();\n    }\n\n    fn access<T>(&self, id: &str) -> ManagedLazy<T> {\n        self.data\n            .get(id)\n            .unwrap()\n            .clone()\n            .into_typed()\n            .ok()\n            .unwrap()\n    }\n}\n\n#[derive(PropsData, Default, Clone, Serialize, Deserialize)]\n#[props_data(raui_core::props::PropsData)]\npub struct ImmediateHooks {\n    #[serde(default, skip)]\n    pre_hooks: Vec<fn(&mut WidgetContext)>,\n    #[serde(default, skip)]\n    post_hooks: Vec<fn(&mut WidgetContext)>,\n}\n\nimpl ImmediateHooks {\n    pub fn with(mut self, pointer: fn(&mut WidgetContext)) -> Self {\n        self.pre_hooks.push(pointer);\n        self\n    }\n\n    pub fn with_post(mut self, pointer: fn(&mut WidgetContext)) -> Self {\n        self.post_hooks.push(pointer);\n        self\n    }\n}\n\nimpl std::fmt::Debug for ImmediateHooks {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(stringify!(ImmediateHooks))\n            .finish_non_exhaustive()\n    }\n}\n\nmacro_rules! impl_lifecycle_props {\n    ($($id:ident),+ $(,)?) => {\n        $(\n            #[derive(PropsData, Default, Clone, Serialize, Deserialize)]\n            #[props_data(raui_core::props::PropsData)]\n            pub struct $id {\n                #[serde(default, skip)]\n                callback: Option<Arc<dyn Fn() + Send + Sync>>,\n            }\n\n            impl $id {\n                pub fn new(callback: impl Fn() + Send + Sync + 'static) -> Self {\n                    Self {\n                        callback: Some(Arc::new(callback)),\n                    }\n                }\n            }\n\n            impl std::fmt::Debug for $id {\n                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n                    f.debug_struct(stringify!($id)).finish_non_exhaustive()\n                }\n            }\n        )+\n    };\n}\n\nimpl_lifecycle_props! {\n    ImmediateOnMount,\n    ImmediateOnChange,\n    ImmediateOnUnmount\n}\n\npub fn use_state<T>(init: impl FnMut() -> T) -> ManagedLazy<T> {\n    STATES.with(|states| {\n        let states = states.borrow();\n        let mut states = states\n            .as_ref()\n            .unwrap_or_else(|| panic!(\"You must activate context first for `use_state` to work!\"))\n            .borrow_mut();\n        states.alloc(init)\n    })\n}\n\npub fn use_access<T>(id: &str) -> ManagedLazy<T> {\n    ACCESS_POINTS.with(|access_points| {\n        let access_points = access_points.borrow();\n        let access_points = access_points\n            .as_ref()\n            .unwrap_or_else(|| panic!(\"You must activate context first for `use_access` to work!\"))\n            .borrow();\n        access_points.access(id)\n    })\n}\n\npub fn use_stack_props<T: PropsData + Clone + 'static>() -> Option<T> {\n    PROPS_STACK.with(|props_stack| {\n        if let Some(props_stack) = props_stack.borrow().as_ref() {\n            for props in props_stack.borrow().iter().rev() {\n                if let Ok(props) = props.read_cloned::<T>() {\n                    return Some(props);\n                }\n            }\n        }\n        None\n    })\n}\n\npub fn use_effects<R>(props: impl Into<Props>, mut f: impl FnMut() -> R) -> R {\n    begin();\n    let result = f();\n    let node = end().pop().unwrap_or_default();\n    push(\n        make_widget!(immediate_effects_box)\n            .merge_props(props.into())\n            .named_slot(\"content\", node),\n    );\n    result\n}\n\npub fn register_access<T>(id: &str, data: &mut T) -> Lifetime {\n    ACCESS_POINTS.with(|access_points| {\n        let access_points = access_points.borrow();\n        let mut access_points = access_points\n            .as_ref()\n            .unwrap_or_else(|| panic!(\"You must activate context first for `use_access` to work!\"))\n            .borrow_mut();\n        access_points.register(id, data)\n    })\n}\n\npub fn begin() {\n    STACK.with(|stack| stack.borrow_mut().push(Default::default()));\n}\n\npub fn end() -> Vec<WidgetNode> {\n    STACK.with(|stack| stack.borrow_mut().pop().unwrap_or_default())\n}\n\npub fn push(widget: impl Into<WidgetNode>) {\n    STACK.with(|stack| {\n        if let Some(widgets) = stack.borrow_mut().last_mut() {\n            widgets.push(widget.into());\n        }\n    });\n}\n\npub fn extend(iter: impl IntoIterator<Item = WidgetNode>) {\n    STACK.with(|stack| {\n        if let Some(widgets) = stack.borrow_mut().last_mut() {\n            widgets.extend(iter);\n        }\n    });\n}\n\npub fn pop() -> WidgetNode {\n    STACK.with(|stack| {\n        stack\n            .borrow_mut()\n            .last_mut()\n            .and_then(|widgets| widgets.pop())\n            .unwrap_or_default()\n    })\n}\n\npub fn reset() {\n    STACK.with(|stack| {\n        stack.borrow_mut().clear();\n    });\n    PROPS_STACK.with(|props_stack| {\n        if let Some(props_stack) = props_stack.borrow_mut().as_mut() {\n            props_stack.borrow_mut().clear();\n        }\n    });\n}\n\npub fn list_component<R>(\n    widget: impl Into<WidgetComponent>,\n    props: impl Into<Props>,\n    mut f: impl FnMut() -> R,\n) -> R {\n    begin();\n    let result = f();\n    let widgets = end();\n    push(\n        widget\n            .into()\n            .merge_props(props.into())\n            .listed_slots(widgets),\n    );\n    result\n}\n\npub fn slot_component<R>(\n    widget: impl Into<WidgetComponent>,\n    props: impl Into<Props>,\n    mut f: impl FnMut() -> R,\n) -> R {\n    begin();\n    let result = f();\n    let widgets = end();\n    let mut list_widgets = Vec::new();\n    let mut slot_widgets = Vec::new();\n    for widget in widgets {\n        if let Some(w) = widget.as_component() {\n            if let Some(name) = w.key.as_deref() {\n                slot_widgets.push((name.to_owned(), widget));\n            } else {\n                list_widgets.push(widget);\n            }\n        }\n    }\n    push(\n        widget\n            .into()\n            .merge_props(props.into())\n            .listed_slots(list_widgets)\n            .named_slots(slot_widgets),\n    );\n    result\n}\n\npub fn content_component<R>(\n    widget: impl Into<WidgetComponent>,\n    content_name: &str,\n    props: impl Into<Props>,\n    mut f: impl FnMut() -> R,\n) -> R {\n    begin();\n    let result = f();\n    let node = end().pop().unwrap_or_default();\n    push(\n        widget\n            .into()\n            .merge_props(props.into())\n            .named_slot(content_name, node),\n    );\n    result\n}\n\npub fn tuple<R>(mut f: impl FnMut() -> R) -> R {\n    begin();\n    let result = f();\n    let widgets = end();\n    push(WidgetNode::Tuple(widgets));\n    result\n}\n\npub fn component(widget: impl Into<WidgetComponent>, props: impl Into<Props>) {\n    push(widget.into().merge_props(props.into()));\n}\n\npub fn unit(widget: impl Into<WidgetUnitNode>) {\n    push(widget.into());\n}\n\npub fn make_widgets(context: &ImmediateContext, mut f: impl FnMut()) -> Vec<WidgetNode> {\n    ImmediateContext::activate(context);\n    begin();\n    f();\n    let result = end();\n    ImmediateContext::deactivate();\n    result\n}\n\npub trait ImmediateApply: Sized {\n    fn before(self) -> Self {\n        self\n    }\n\n    fn after(self) -> Self {\n        self\n    }\n\n    fn process(self, widgets: Vec<WidgetNode>) -> Vec<WidgetNode> {\n        widgets\n    }\n}\n\nmacro_rules! impl_tuple_immediate_apply {\n    ($($id:ident),+ $(,)?) => {\n        #[allow(non_snake_case)]\n        impl<$($id: $crate::ImmediateApply),+> $crate::ImmediateApply for ($($id,)+) {\n            fn before(self) -> Self {\n                let ($($id,)+) = self;\n                (\n                    $(\n                        $id.before(),\n                    )+\n                )\n            }\n\n            fn after(self) -> Self {\n                let ($($id,)+) = self;\n                (\n                    $(\n                        $id.after(),\n                    )+\n                )\n            }\n\n            fn process(self, mut widgets: Vec<WidgetNode>) -> Vec<WidgetNode> {\n                let ($($id,)+) = self;\n                $(\n                    widgets = $id.process(widgets);\n                )+\n                widgets\n            }\n        }\n    };\n}\n\nimpl_tuple_immediate_apply!(A);\nimpl_tuple_immediate_apply!(A, B);\nimpl_tuple_immediate_apply!(A, B, C);\nimpl_tuple_immediate_apply!(A, B, C, D);\nimpl_tuple_immediate_apply!(A, B, C, D, E);\nimpl_tuple_immediate_apply!(A, B, C, D, E, F);\nimpl_tuple_immediate_apply!(A, B, C, D, E, F, G);\nimpl_tuple_immediate_apply!(A, B, C, D, E, F, G, H);\nimpl_tuple_immediate_apply!(A, B, C, D, E, F, G, H, I);\nimpl_tuple_immediate_apply!(A, B, C, D, E, F, G, H, I, J);\nimpl_tuple_immediate_apply!(A, B, C, D, E, F, G, H, I, J, K);\nimpl_tuple_immediate_apply!(A, B, C, D, E, F, G, H, I, J, K, L);\nimpl_tuple_immediate_apply!(A, B, C, D, E, F, G, H, I, J, K, L, M);\nimpl_tuple_immediate_apply!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);\nimpl_tuple_immediate_apply!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);\nimpl_tuple_immediate_apply!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);\nimpl_tuple_immediate_apply!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q);\nimpl_tuple_immediate_apply!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R);\nimpl_tuple_immediate_apply!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S);\nimpl_tuple_immediate_apply!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T);\nimpl_tuple_immediate_apply!(\n    A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U\n);\nimpl_tuple_immediate_apply!(\n    A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V\n);\nimpl_tuple_immediate_apply!(\n    A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, X\n);\nimpl_tuple_immediate_apply!(\n    A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, X, Y\n);\nimpl_tuple_immediate_apply!(\n    A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, X, Y, Z\n);\n\npub struct ImKey<T: ToString>(pub T);\n\nimpl<T: ToString> ImmediateApply for ImKey<T> {\n    fn process(self, mut widgets: Vec<WidgetNode>) -> Vec<WidgetNode> {\n        let key = self.0.to_string();\n        match widgets.len() {\n            0 => {}\n            1 => {\n                if let WidgetNode::Component(widget) = &mut widgets[0] {\n                    widget.key = Some(key);\n                }\n            }\n            _ => {\n                for (index, widget) in widgets.iter_mut().enumerate() {\n                    if let WidgetNode::Component(widget) = widget {\n                        widget.key = Some(format!(\"{key}-{index}\"));\n                    }\n                }\n            }\n        }\n        widgets\n    }\n}\n\npub struct ImIdRef<T: Into<WidgetRef>>(pub T);\n\nimpl<T: Into<WidgetRef>> ImmediateApply for ImIdRef<T> {\n    fn process(self, mut widgets: Vec<WidgetNode>) -> Vec<WidgetNode> {\n        let idref = self.0.into();\n        for widget in &mut widgets {\n            if let WidgetNode::Component(widget) = widget {\n                widget.idref = Some(idref.clone());\n            }\n        }\n        widgets\n    }\n}\n\npub struct ImProps<T: Into<Props>>(pub T);\n\nimpl<T: Into<Props>> ImmediateApply for ImProps<T> {\n    fn process(self, mut widgets: Vec<WidgetNode>) -> Vec<WidgetNode> {\n        let props = self.0.into();\n        for widget in &mut widgets {\n            if let Some(widget) = widget.props_mut() {\n                widget.merge_from(props.clone());\n            }\n        }\n        widgets\n    }\n}\n\npub struct ImSharedProps<T: Into<Props>>(pub T);\n\nimpl<T: Into<Props>> ImmediateApply for ImSharedProps<T> {\n    fn process(self, mut widgets: Vec<WidgetNode>) -> Vec<WidgetNode> {\n        let props = self.0.into();\n        for widget in &mut widgets {\n            if let Some(widget) = widget.shared_props_mut() {\n                widget.merge_from(props.clone());\n            }\n        }\n        widgets\n    }\n}\n\npub enum ImStackProps<T: Into<Props>> {\n    Props(T),\n    Done,\n}\n\nimpl<T: Into<Props>> ImStackProps<T> {\n    pub fn new(props: T) -> Self {\n        Self::Props(props)\n    }\n}\n\nimpl<T: Into<Props>> ImmediateApply for ImStackProps<T> {\n    fn before(self) -> Self {\n        if let Self::Props(props) = self {\n            let props = props.into();\n            PROPS_STACK.with(|props_stack| {\n                if let Some(props_stack) = props_stack.borrow_mut().as_mut() {\n                    props_stack.borrow_mut().push(props.clone());\n                }\n            });\n        }\n        Self::Done\n    }\n\n    fn after(self) -> Self {\n        if let Self::Done = self {\n            PROPS_STACK.with(|props_stack| {\n                if let Some(props_stack) = props_stack.borrow_mut().as_mut() {\n                    props_stack.borrow_mut().pop();\n                }\n            });\n        }\n        self\n    }\n}\n\npub fn apply<R>(items: impl ImmediateApply, mut f: impl FnMut() -> R) -> R {\n    begin();\n    let items = items.before();\n    let result = f();\n    let items = items.after();\n    let widgets = end();\n    let widgets = items.process(widgets);\n    extend(widgets);\n    result\n}\n\n#[deprecated(note = \"Use `apply` with `ImKey` instead\")]\npub fn apply_key<R>(key: impl ToString, f: impl FnMut() -> R) -> R {\n    apply(ImKey(key), f)\n}\n\n#[deprecated(note = \"Use `apply` with `ImIdRef` instead\")]\npub fn apply_idref<R>(key: impl Into<WidgetRef>, f: impl FnMut() -> R) -> R {\n    apply(ImIdRef(key), f)\n}\n\n#[deprecated(note = \"Use `apply` with `ImProps` instead\")]\npub fn apply_props<R>(props: impl Into<Props>, f: impl FnMut() -> R) -> R {\n    apply(ImProps(props), f)\n}\n\n#[deprecated(note = \"Use `apply` with `ImSharedProps` instead\")]\npub fn apply_shared_props<R>(props: impl Into<Props>, f: impl FnMut() -> R) -> R {\n    apply(ImSharedProps(props), f)\n}\n\n#[deprecated(note = \"Use `apply` with `ImStackProps` instead\")]\npub fn stack_props<R>(props: impl Into<Props>, f: impl FnMut() -> R) -> R {\n    apply(ImStackProps::new(props), f)\n}\n\nmod internal {\n    use super::*;\n    use raui_core::widget::unit::area::AreaBoxNode;\n\n    pub(crate) fn immediate_effects_box(mut ctx: WidgetContext) -> WidgetNode {\n        let hooks = ctx.props.read_cloned_or_default::<ImmediateHooks>();\n        for hook in &hooks.pre_hooks {\n            hook(&mut ctx);\n        }\n\n        if let Ok(event) = ctx.props.read::<ImmediateOnMount>()\n            && let Some(callback) = event.callback.as_ref()\n        {\n            let callback = callback.clone();\n            ctx.life_cycle.mount(move |_| {\n                callback();\n            });\n        }\n        if let Ok(event) = ctx.props.read::<ImmediateOnChange>()\n            && let Some(callback) = event.callback.as_ref()\n        {\n            let callback = callback.clone();\n            ctx.life_cycle.change(move |_| {\n                callback();\n            });\n        }\n        if let Ok(event) = ctx.props.read::<ImmediateOnUnmount>()\n            && let Some(callback) = event.callback.as_ref()\n        {\n            let callback = callback.clone();\n            ctx.life_cycle.unmount(move |_| {\n                callback();\n            });\n        }\n\n        let content = ctx.named_slots.remove(\"content\").unwrap_or_default();\n\n        let result = AreaBoxNode {\n            id: ctx.id.to_owned(),\n            slot: Box::new(content),\n        };\n        for hook in &hooks.post_hooks {\n            hook(&mut ctx);\n        }\n        result.into()\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use raui_core::widget::component::image_box::{ImageBoxProps, image_box};\n\n    use super::*;\n\n    fn run(frame: usize) {\n        let show_slider = use_state(|| false);\n        let mut show_slider = show_slider.write().unwrap();\n\n        let show_text_field = use_state(|| false);\n        let mut show_text_field = show_text_field.write().unwrap();\n\n        if frame == 1 {\n            *show_slider = true;\n        } else if frame == 3 {\n            *show_text_field = true;\n        } else if frame == 5 {\n            *show_slider = false;\n        } else if frame == 7 {\n            *show_text_field = false;\n        } else if frame == 9 {\n            *show_slider = true;\n            *show_text_field = true;\n        }\n\n        println!(\n            \"* #{} | HOVERED: {} | CLICKED: {}\",\n            frame, *show_slider, *show_text_field\n        );\n\n        if *show_slider {\n            slider();\n        }\n        if *show_text_field {\n            text_field();\n        }\n    }\n\n    fn slider() {\n        let value = use_state(|| 0.0);\n        let mut state = value.write().unwrap();\n\n        *state += 0.1;\n        println!(\"* SLIDER VALUE: {}\", *state);\n    }\n\n    fn text_field() {\n        let text = use_state(String::default);\n        let mut text = text.write().unwrap();\n\n        text.push('z');\n\n        println!(\"* TEXT FIELD: {}\", text.as_str());\n    }\n\n    #[test]\n    fn test_use_state() {\n        let context = ImmediateContext::default();\n        for frame in 0..12 {\n            ImmediateContext::activate(&context);\n            run(frame);\n            ImmediateContext::deactivate();\n        }\n    }\n\n    #[test]\n    fn test_apply() {\n        let context = ImmediateContext::default();\n        ImmediateContext::activate(&context);\n        begin();\n\n        apply(\n            (\n                ImKey(\"image\"),\n                ImProps(ImageBoxProps::colored(Default::default())),\n            ),\n            || {\n                component(make_widget!(image_box), ());\n            },\n        );\n\n        let widgets = end();\n        ImmediateContext::deactivate();\n\n        assert_eq!(widgets.len(), 1);\n        if let WidgetNode::Component(component) = &widgets[0] {\n            assert_eq!(component.key.as_deref(), Some(\"image\"));\n            assert!(component.props.has::<ImageBoxProps>());\n        } else {\n            panic!(\"Expected a component node\");\n        }\n    }\n}\n"
  },
  {
    "path": "crates/immediate-widgets/Cargo.toml",
    "content": "[package]\nname = \"raui-immediate-widgets\"\nversion = \"0.70.17\"\nauthors = [\"Patryk 'PsichiX' Budzynski <psichix@gmail.com>\"]\nedition = \"2024\"\ndescription = \"Widgets library for RAUI immediate mode UI layer\"\nreadme = \"../../README.md\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/RAUI-labs/raui\"\nkeywords = [\"renderer\", \"agnostic\", \"ui\", \"interface\", \"gamedev\"]\ncategories = [\"gui\", \"rendering::graphics-api\"]\n\n[dependencies]\nraui-core = { path = \"../core\", version = \"0.70\" }\nraui-material = { path = \"../material\", version = \"0.70\" }\nraui-immediate = { path = \"../immediate\", version = \"0.70\" }\nserde = { version = \"1\", features = [\"derive\"] }\n"
  },
  {
    "path": "crates/immediate-widgets/src/lib.rs",
    "content": "use raui_immediate::*;\n\nmacro_rules! impl_imports {\n    () => {\n        #[allow(unused_imports)]\n        use raui_core::widget::component::{\n            containers::{\n                anchor_box::*, area_box::*, content_box::*, context_box::*, flex_box::*,\n                float_box::*, grid_box::*, hidden_box::*, horizontal_box::*, portal_box::*,\n                responsive_box::*, scroll_box::*, size_box::*, switch_box::*, tabs_box::*,\n                tooltip_box::*, variant_box::*, vertical_box::*, wrap_box::*,\n            },\n            interactive::{\n                button::*, float_view::*, input_field::*, navigation::*, options_view::*,\n                scroll_view::*, slider_view::*,\n            },\n        };\n        #[allow(unused_imports)]\n        use raui_core::widget::{\n            component::{image_box::*, space_box::*, text_box::*},\n            none_widget,\n        };\n        #[allow(unused_imports)]\n        use raui_material::component::{\n            containers::{\n                context_paper::*, flex_paper::*, grid_paper::*, horizontal_paper::*,\n                modal_paper::*, paper::*, scroll_paper::*, text_tooltip_paper::*, tooltip_paper::*,\n                vertical_paper::*, window_paper::*, wrap_paper::*,\n            },\n            interactive::{\n                button_paper::*, icon_button_paper::*, slider_paper::*, switch_button_paper::*,\n                text_button_paper::*, text_field_paper::*,\n            },\n        };\n        #[allow(unused_imports)]\n        use raui_material::component::{icon_paper::*, switch_paper::*, text_paper::*};\n    };\n}\n\nmacro_rules! impl_slot_components {\n    ($($name:ident),+ $(,)?) => {\n        $(\n            pub fn $name<R>(\n                props: impl Into<raui_core::props::Props>,\n                f: impl FnMut() -> R,\n            ) -> R {\n                impl_imports!();\n                crate::slot_component(raui_core::make_widget!($name), props, f)\n            }\n        )+\n    };\n}\n\nmacro_rules! impl_list_components {\n    ($($name:ident),+ $(,)?) => {\n        $(\n            pub fn $name<R>(\n                props: impl Into<raui_core::props::Props>,\n                f: impl FnMut() -> R,\n            ) -> R {\n                impl_imports!();\n                crate::list_component(raui_core::make_widget!($name), props, f)\n            }\n        )+\n    };\n}\n\nmacro_rules! impl_content_components {\n    ($content:literal : $($name:ident),+ $(,)?) => {\n        $(\n            pub fn $name<R>(\n                props: impl Into<raui_core::props::Props>,\n                f: impl FnMut() -> R,\n            ) -> R {\n                impl_imports!();\n                crate::content_component(raui_core::make_widget!($name), $content, props, f)\n            }\n        )+\n    };\n}\n\nmacro_rules! impl_components {\n    ($($name:ident),+ $(,)?) => {\n        $(\n            pub fn $name(\n                props: impl Into<raui_core::props::Props>,\n            ) {\n                impl_imports!();\n                crate::component(raui_core::make_widget!($name), props)\n            }\n        )+\n    };\n}\n\npub mod core {\n    impl_components! {\n        image_box,\n        nav_scroll_box_side_scrollbars,\n        none_widget,\n        space_box,\n        text_box,\n    }\n\n    pub mod containers {\n        impl_content_components! {\n            \"content\":\n            anchor_box,\n            area_box,\n            hidden_box,\n            nav_scroll_box_content,\n            pivot_box,\n            portal_box,\n            size_box,\n            wrap_box,\n        }\n\n        impl_slot_components! {\n            context_box,\n            nav_scroll_box,\n            portals_context_box,\n            portals_tooltip_box,\n            responsive_props_box,\n            tooltip_box,\n            variant_box,\n        }\n\n        impl_list_components! {\n            content_box,\n            flex_box,\n            float_box,\n            grid_box,\n            horizontal_box,\n            nav_content_box,\n            nav_flex_box,\n            nav_float_box,\n            nav_grid_box,\n            nav_horizontal_box,\n            nav_switch_box,\n            nav_tabs_box,\n            nav_vertical_box,\n            responsive_box,\n            switch_box,\n            vertical_box,\n        }\n    }\n\n    pub mod interactive {\n        use raui_core::{\n            make_widget,\n            props::Props,\n            widget::{\n                component::interactive::{\n                    button::ButtonProps,\n                    input_field::{TextInputProps, TextInputState},\n                    navigation::NavTrackingProps,\n                    options_view::{OptionsViewProps, OptionsViewProxy},\n                    slider_view::{SliderViewProps, SliderViewProxy},\n                },\n                utils::Vec2,\n            },\n        };\n        use raui_immediate::{begin, end, pop, push, use_state};\n        use std::str::FromStr;\n\n        #[derive(Debug, Default, Copy, Clone)]\n        pub struct ImmediateTracking {\n            pub state: NavTrackingProps,\n            pub prev: NavTrackingProps,\n        }\n\n        impl ImmediateTracking {\n            pub fn pointer_delta_factor(&self) -> Vec2 {\n                Vec2 {\n                    x: self.state.factor.x - self.prev.factor.x,\n                    y: self.state.factor.y - self.prev.factor.y,\n                }\n            }\n\n            pub fn pointer_delta_unscaled(&self) -> Vec2 {\n                Vec2 {\n                    x: self.state.unscaled.x - self.prev.unscaled.x,\n                    y: self.state.unscaled.y - self.prev.unscaled.y,\n                }\n            }\n\n            pub fn pointer_delta_ui_space(&self) -> Vec2 {\n                Vec2 {\n                    x: self.state.ui_space.x - self.prev.ui_space.x,\n                    y: self.state.ui_space.y - self.prev.ui_space.y,\n                }\n            }\n\n            pub fn pointer_moved(&self) -> bool {\n                (self.state.factor.x - self.prev.factor.x)\n                    + (self.state.factor.y - self.prev.factor.y)\n                    > 1.0e-6\n            }\n        }\n\n        #[derive(Debug, Default, Copy, Clone)]\n        pub struct ImmediateButton {\n            pub state: ButtonProps,\n            pub prev: ButtonProps,\n        }\n\n        impl ImmediateButton {\n            pub fn select_start(&self) -> bool {\n                !self.prev.selected && self.state.selected\n            }\n\n            pub fn select_stop(&self) -> bool {\n                self.prev.selected && !self.state.selected\n            }\n\n            pub fn select_changed(&self) -> bool {\n                self.prev.selected != self.state.selected\n            }\n\n            pub fn trigger_start(&self) -> bool {\n                !self.prev.trigger && self.state.trigger\n            }\n\n            pub fn trigger_stop(&self) -> bool {\n                self.prev.trigger && !self.state.trigger\n            }\n\n            pub fn trigger_changed(&self) -> bool {\n                self.prev.trigger != self.state.trigger\n            }\n\n            pub fn context_start(&self) -> bool {\n                !self.prev.context && self.state.context\n            }\n\n            pub fn context_stop(&self) -> bool {\n                self.prev.context && !self.state.context\n            }\n\n            pub fn context_changed(&self) -> bool {\n                self.prev.context != self.state.context\n            }\n        }\n\n        impl_content_components! {\n            \"content\":\n            float_view_control,\n            navigation_barrier,\n        }\n\n        pub fn tracking(\n            props: impl Into<Props>,\n            mut f: impl FnMut(ImmediateTracking),\n        ) -> ImmediateTracking {\n            use crate::internal::*;\n            let state = use_state(ImmediateTracking::default);\n            let result = state.read().unwrap().to_owned();\n            begin();\n            f(result);\n            let node = end().pop().unwrap_or_default();\n            push(\n                make_widget!(immediate_tracking)\n                    .with_props(ImmediateTrackingProps { state: Some(state) })\n                    .merge_props(props.into())\n                    .named_slot(\"content\", node),\n            );\n            result\n        }\n\n        pub fn self_tracking(\n            props: impl Into<Props>,\n            mut f: impl FnMut(ImmediateTracking),\n        ) -> ImmediateTracking {\n            use crate::internal::*;\n            let state = use_state(ImmediateTracking::default);\n            let result = state.read().unwrap().to_owned();\n            begin();\n            f(result);\n            let node = end().pop().unwrap_or_default();\n            push(\n                make_widget!(immediate_self_tracking)\n                    .with_props(ImmediateTrackingProps { state: Some(state) })\n                    .merge_props(props.into())\n                    .named_slot(\"content\", node),\n            );\n            result\n        }\n\n        pub fn button(\n            props: impl Into<Props>,\n            mut f: impl FnMut(ImmediateButton),\n        ) -> ImmediateButton {\n            use crate::internal::*;\n            let state = use_state(ImmediateButton::default);\n            let result = state.read().unwrap().to_owned();\n            begin();\n            f(result);\n            let node = end().pop().unwrap_or_default();\n            push(\n                make_widget!(immediate_button)\n                    .with_props(ImmediateButtonProps { state: Some(state) })\n                    .merge_props(props.into())\n                    .named_slot(\"content\", node),\n            );\n            result\n        }\n\n        pub fn tracked_button(\n            props: impl Into<Props>,\n            mut f: impl FnMut(ImmediateButton),\n        ) -> ImmediateButton {\n            use crate::internal::*;\n            let state = use_state(ImmediateButton::default);\n            let result = state.read().unwrap().to_owned();\n            begin();\n            f(result);\n            let node = end().pop().unwrap_or_default();\n            push(\n                make_widget!(immediate_tracked_button)\n                    .with_props(ImmediateButtonProps { state: Some(state) })\n                    .merge_props(props.into())\n                    .named_slot(\"content\", node),\n            );\n            result\n        }\n\n        pub fn self_tracked_button(\n            props: impl Into<Props>,\n            mut f: impl FnMut(ImmediateButton),\n        ) -> ImmediateButton {\n            use crate::internal::*;\n            let state = use_state(ImmediateButton::default);\n            let result = state.read().unwrap().to_owned();\n            begin();\n            f(result);\n            let node = end().pop().unwrap_or_default();\n            push(\n                make_widget!(immediate_self_tracked_button)\n                    .with_props(ImmediateButtonProps { state: Some(state) })\n                    .merge_props(props.into())\n                    .named_slot(\"content\", node),\n            );\n            result\n        }\n\n        pub fn text_input<T: ToString + FromStr + Send + Sync>(\n            value: &T,\n            props: impl Into<Props>,\n            mut f: impl FnMut(&str, TextInputState),\n        ) -> (Option<T>, TextInputState) {\n            use crate::internal::*;\n            let content = use_state(|| value.to_string());\n            let props = props.into();\n            let TextInputProps { allow_new_line, .. } = props.read_cloned_or_default();\n            let text_state = use_state(TextInputState::default);\n            let text_result = text_state.read().unwrap().to_owned();\n            if !text_result.focused {\n                *content.write().unwrap() = value.to_string();\n            }\n            let result = content.read().unwrap().to_string();\n            begin();\n            f(&result, text_result);\n            let node = end().pop().unwrap_or_default();\n            push(\n                make_widget!(immediate_text_input)\n                    .with_props(ImmediateTextInputProps {\n                        state: Some(text_state),\n                    })\n                    .merge_props(props)\n                    .with_props(TextInputProps {\n                        allow_new_line,\n                        text: Some(content.into()),\n                    })\n                    .named_slot(\"content\", node),\n            );\n            (result.parse().ok(), text_result)\n        }\n\n        pub fn input_field<T: ToString + FromStr + Send + Sync>(\n            value: &T,\n            props: impl Into<Props>,\n            mut f: impl FnMut(&str, TextInputState, ImmediateButton),\n        ) -> (Option<T>, TextInputState, ImmediateButton) {\n            use crate::internal::*;\n            let content = use_state(|| value.to_string());\n            let props = props.into();\n            let TextInputProps { allow_new_line, .. } = props.read_cloned_or_default();\n            let text_state = use_state(TextInputState::default);\n            let text_result = text_state.read().unwrap().to_owned();\n            let button_state = use_state(ImmediateButton::default);\n            let button_result = button_state.read().unwrap().to_owned();\n            if !text_result.focused {\n                *content.write().unwrap() = value.to_string();\n            }\n            let result = content.read().unwrap().to_string();\n            begin();\n            f(&result, text_result, button_result);\n            let node = end().pop().unwrap_or_default();\n            push(\n                make_widget!(immediate_input_field)\n                    .with_props(ImmediateTextInputProps {\n                        state: Some(text_state),\n                    })\n                    .with_props(ImmediateButtonProps {\n                        state: Some(button_state),\n                    })\n                    .merge_props(props)\n                    .with_props(TextInputProps {\n                        allow_new_line,\n                        text: Some(content.into()),\n                    })\n                    .named_slot(\"content\", node),\n            );\n            (result.parse().ok(), text_result, button_result)\n        }\n\n        pub fn slider_view<T: SliderViewProxy + Clone + 'static>(\n            value: T,\n            props: impl Into<Props>,\n            mut f: impl FnMut(&T, ImmediateButton),\n        ) -> (T, ImmediateButton) {\n            use crate::internal::*;\n            let content = use_state(|| value.to_owned());\n            let props = props.into();\n            let SliderViewProps {\n                from,\n                to,\n                direction,\n                ..\n            } = props.read_cloned_or_default();\n            let button_state = use_state(ImmediateButton::default);\n            let button_result = button_state.read().unwrap().to_owned();\n            let result = content.read().unwrap().to_owned();\n            begin();\n            f(&result, button_result);\n            let node = end().pop().unwrap_or_default();\n            push(\n                make_widget!(immediate_slider_view)\n                    .with_props(ImmediateButtonProps {\n                        state: Some(button_state),\n                    })\n                    .merge_props(props)\n                    .with_props(SliderViewProps {\n                        input: Some(content.into()),\n                        from,\n                        to,\n                        direction,\n                    })\n                    .named_slot(\"content\", node),\n            );\n            (result, button_result)\n        }\n\n        pub fn options_view<T: OptionsViewProxy + Clone + 'static>(\n            value: T,\n            props: impl Into<Props>,\n            mut f_items: impl FnMut(&T),\n            mut f_content: impl FnMut(),\n        ) -> T {\n            let content = use_state(|| value.to_owned());\n            let props = props.into();\n            let result = content.read().unwrap().to_owned();\n            begin();\n            f_items(&result);\n            let nodes = end();\n            begin();\n            f_content();\n            let node = pop();\n            push(\n                make_widget!(raui_core::widget::component::interactive::options_view::options_view)\n                    .merge_props(props)\n                    .with_props(OptionsViewProps {\n                        input: Some(content.into()),\n                    })\n                    .named_slot(\"content\", node)\n                    .listed_slots(nodes),\n            );\n            result\n        }\n    }\n}\n\npub mod material {\n    impl_components! {\n        icon_paper,\n        scroll_paper_side_scrollbars,\n        switch_paper,\n        text_paper,\n    }\n\n    pub mod containers {\n        impl_slot_components! {\n            context_paper,\n            scroll_paper,\n            tooltip_paper,\n            window_paper,\n            window_title_controls_paper,\n        }\n\n        impl_content_components! {\n            \"content\":\n            modal_paper,\n            text_tooltip_paper,\n            wrap_paper,\n        }\n\n        impl_list_components! {\n            flex_paper,\n            grid_paper,\n            horizontal_paper,\n            nav_flex_paper,\n            nav_grid_paper,\n            nav_horizontal_paper,\n            nav_paper,\n            nav_vertical_paper,\n            paper,\n            vertical_paper,\n        }\n    }\n\n    pub mod interactive {\n        use crate::core::interactive::ImmediateButton;\n        use raui_core::{\n            make_widget,\n            props::Props,\n            widget::component::interactive::{\n                input_field::{TextInputProps, TextInputState},\n                slider_view::{SliderViewProps, SliderViewProxy},\n            },\n        };\n        use raui_immediate::{begin, end, push, use_state};\n        use std::str::FromStr;\n\n        pub fn button_paper(\n            props: impl Into<Props>,\n            mut f: impl FnMut(ImmediateButton),\n        ) -> ImmediateButton {\n            use crate::internal::*;\n            let state = use_state(ImmediateButton::default);\n            let result = state.read().unwrap().to_owned();\n            begin();\n            f(result);\n            let node = end().pop().unwrap_or_default();\n            push(\n                make_widget!(immediate_button_paper)\n                    .with_props(ImmediateButtonProps { state: Some(state) })\n                    .merge_props(props.into())\n                    .named_slot(\"content\", node),\n            );\n            result\n        }\n\n        pub fn icon_button_paper(props: impl Into<Props>) -> ImmediateButton {\n            use crate::internal::*;\n            let state = use_state(ImmediateButton::default);\n            let result = state.read().unwrap().to_owned();\n            push(\n                make_widget!(immediate_icon_button_paper)\n                    .with_props(ImmediateButtonProps { state: Some(state) })\n                    .merge_props(props.into()),\n            );\n            result\n        }\n\n        pub fn switch_button_paper(props: impl Into<Props>) -> ImmediateButton {\n            use crate::internal::*;\n            let state = use_state(ImmediateButton::default);\n            let result = state.read().unwrap().to_owned();\n            push(\n                make_widget!(immediate_switch_button_paper)\n                    .with_props(ImmediateButtonProps { state: Some(state) })\n                    .merge_props(props.into()),\n            );\n            result\n        }\n\n        pub fn text_button_paper(props: impl Into<Props>) -> ImmediateButton {\n            use crate::internal::*;\n            let state = use_state(ImmediateButton::default);\n            let result = state.read().unwrap().to_owned();\n            push(\n                make_widget!(immediate_text_button_paper)\n                    .with_props(ImmediateButtonProps { state: Some(state) })\n                    .merge_props(props.into()),\n            );\n            result\n        }\n\n        pub fn text_field_paper<T: ToString + FromStr + Send + Sync>(\n            value: &T,\n            props: impl Into<Props>,\n        ) -> (Option<T>, TextInputState, ImmediateButton) {\n            use crate::internal::*;\n            let content = use_state(|| value.to_string());\n            let props = props.into();\n            let TextInputProps { allow_new_line, .. } =\n                props.read_cloned_or_default::<TextInputProps>();\n            let text_state = use_state(TextInputState::default);\n            let text_result = text_state.read().unwrap().to_owned();\n            let button_state = use_state(ImmediateButton::default);\n            let button_result = button_state.read().unwrap().to_owned();\n            if !text_result.focused {\n                *content.write().unwrap() = value.to_string();\n            }\n            let result = content.read().unwrap().to_string();\n            push(\n                make_widget!(immediate_text_field_paper)\n                    .with_props(ImmediateTextInputProps {\n                        state: Some(text_state),\n                    })\n                    .with_props(ImmediateButtonProps {\n                        state: Some(button_state),\n                    })\n                    .merge_props(props)\n                    .with_props(TextInputProps {\n                        allow_new_line,\n                        text: Some(content.into()),\n                    }),\n            );\n            (result.parse().ok(), text_result, button_result)\n        }\n\n        pub fn slider_paper<T: SliderViewProxy + Clone + 'static>(\n            value: T,\n            props: impl Into<Props>,\n            mut f: impl FnMut(&T, ImmediateButton),\n        ) -> (T, ImmediateButton) {\n            use crate::internal::*;\n            let content = use_state(|| value.to_owned());\n            let props = props.into();\n            let SliderViewProps {\n                from,\n                to,\n                direction,\n                ..\n            } = props.read_cloned_or_default();\n            let button_state = use_state(ImmediateButton::default);\n            let button_result = button_state.read().unwrap().to_owned();\n            let result = content.read().unwrap().to_owned();\n            begin();\n            f(&result, button_result);\n            let node = end().pop().unwrap_or_default();\n            push(\n                make_widget!(immediate_slider_paper)\n                    .with_props(ImmediateButtonProps {\n                        state: Some(button_state),\n                    })\n                    .merge_props(props)\n                    .with_props(SliderViewProps {\n                        input: Some(content.into()),\n                        from,\n                        to,\n                        direction,\n                    })\n                    .named_slot(\"content\", node),\n            );\n            (result, button_result)\n        }\n\n        pub fn numeric_slider_paper<T: SliderViewProxy + Clone + 'static>(\n            value: T,\n            props: impl Into<Props>,\n        ) -> (T, ImmediateButton) {\n            use crate::internal::*;\n            let content = use_state(|| value.to_owned());\n            let props = props.into();\n            let SliderViewProps {\n                from,\n                to,\n                direction,\n                ..\n            } = props.read_cloned_or_default();\n            let button_state = use_state(ImmediateButton::default);\n            let button_result = button_state.read().unwrap().to_owned();\n            let result = content.read().unwrap().to_owned();\n            push(\n                make_widget!(immediate_numeric_slider_paper)\n                    .with_props(ImmediateButtonProps {\n                        state: Some(button_state),\n                    })\n                    .merge_props(props)\n                    .with_props(SliderViewProps {\n                        input: Some(content.into()),\n                        from,\n                        to,\n                        direction,\n                    }),\n            );\n            (result, button_result)\n        }\n    }\n}\n\nmod internal {\n    use crate::core::interactive::{ImmediateButton, ImmediateTracking};\n    use raui_core::{\n        ManagedLazy, Prefab, PropsData, make_widget, pre_hooks,\n        widget::{\n            component::interactive::{\n                button::{\n                    ButtonNotifyMessage, ButtonNotifyProps, button, self_tracked_button,\n                    tracked_button,\n                },\n                input_field::{TextInputState, input_field, text_input},\n                navigation::{\n                    NavTrackingNotifyMessage, NavTrackingNotifyProps, self_tracking, tracking,\n                    use_nav_tracking_self,\n                },\n                slider_view::slider_view,\n            },\n            context::WidgetContext,\n            node::WidgetNode,\n        },\n    };\n    use raui_material::component::interactive::{\n        button_paper::button_paper_impl,\n        icon_button_paper::icon_button_paper_impl,\n        slider_paper::{numeric_slider_paper_impl, slider_paper_impl},\n        switch_button_paper::switch_button_paper_impl,\n        text_button_paper::text_button_paper_impl,\n        text_field_paper::text_field_paper_impl,\n    };\n    use serde::{Deserialize, Serialize};\n\n    #[derive(PropsData, Default, Clone, Serialize, Deserialize)]\n    #[props_data(raui_core::props::PropsData)]\n    #[prefab(raui_core::Prefab)]\n    pub struct ImmediateTrackingProps {\n        #[serde(default, skip)]\n        pub state: Option<ManagedLazy<ImmediateTracking>>,\n    }\n\n    impl std::fmt::Debug for ImmediateTrackingProps {\n        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n            f.debug_struct(\"ImmediateTrackingProps\")\n                .field(\n                    \"state\",\n                    &self\n                        .state\n                        .as_ref()\n                        .and_then(|state| state.read())\n                        .map(|state| *state),\n                )\n                .finish()\n        }\n    }\n\n    #[derive(PropsData, Default, Clone, Serialize, Deserialize)]\n    #[props_data(raui_core::props::PropsData)]\n    #[prefab(raui_core::Prefab)]\n    pub struct ImmediateButtonProps {\n        #[serde(default, skip)]\n        pub state: Option<ManagedLazy<ImmediateButton>>,\n    }\n\n    impl std::fmt::Debug for ImmediateButtonProps {\n        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n            f.debug_struct(\"ImmediateButtonProps\")\n                .field(\n                    \"state\",\n                    &self\n                        .state\n                        .as_ref()\n                        .and_then(|state| state.read())\n                        .map(|state| *state),\n                )\n                .finish()\n        }\n    }\n\n    #[derive(PropsData, Default, Clone, Serialize, Deserialize)]\n    #[props_data(raui_core::props::PropsData)]\n    pub struct ImmediateTextInputProps {\n        #[serde(default, skip)]\n        pub state: Option<ManagedLazy<TextInputState>>,\n    }\n\n    impl std::fmt::Debug for ImmediateTextInputProps {\n        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n            f.debug_struct(\"ImmediateTextInputProps\")\n                .finish_non_exhaustive()\n        }\n    }\n\n    fn use_immediate_tracking(ctx: &mut WidgetContext) {\n        ctx.props\n            .write(NavTrackingNotifyProps(ctx.id.to_owned().into()));\n\n        if let Ok(props) = ctx.props.read::<ImmediateTrackingProps>() {\n            let state = props.state.as_ref().unwrap();\n            let mut state = state.write().unwrap();\n            state.prev = state.state;\n        }\n\n        ctx.life_cycle.change(|ctx| {\n            if let Ok(props) = ctx.props.read::<ImmediateTrackingProps>()\n                && let Some(state) = props.state.as_ref()\n                && let Some(mut state) = state.write()\n            {\n                for msg in ctx.messenger.messages {\n                    if let Some(msg) = msg.as_any().downcast_ref::<NavTrackingNotifyMessage>() {\n                        state.state = msg.state;\n                    }\n                }\n            }\n        });\n    }\n\n    fn use_immediate_button(ctx: &mut WidgetContext) {\n        ctx.props.write(ButtonNotifyProps(ctx.id.to_owned().into()));\n\n        if let Ok(props) = ctx.props.read::<ImmediateButtonProps>() {\n            let state = props.state.as_ref().unwrap();\n            let mut state = state.write().unwrap();\n            state.prev = state.state;\n        }\n\n        ctx.life_cycle.change(|ctx| {\n            if let Ok(props) = ctx.props.read::<ImmediateButtonProps>()\n                && let Some(state) = props.state.as_ref()\n                && let Some(mut state) = state.write()\n            {\n                for msg in ctx.messenger.messages {\n                    if let Some(msg) = msg.as_any().downcast_ref::<ButtonNotifyMessage>() {\n                        state.state = msg.state;\n                    }\n                }\n            }\n        });\n    }\n\n    fn use_immediate_text_input(ctx: &mut WidgetContext) {\n        if let Ok(data) = ctx.state.read_cloned::<TextInputState>()\n            && let Ok(props) = ctx.props.read::<ImmediateTextInputProps>()\n        {\n            let state = props.state.as_ref().unwrap();\n            let mut state = state.write().unwrap();\n            *state = data;\n        }\n    }\n\n    #[pre_hooks(use_immediate_tracking)]\n    pub(crate) fn immediate_tracking(mut ctx: WidgetContext) -> WidgetNode {\n        tracking(ctx)\n    }\n\n    #[pre_hooks(use_immediate_tracking, use_nav_tracking_self)]\n    pub(crate) fn immediate_self_tracking(mut ctx: WidgetContext) -> WidgetNode {\n        self_tracking(ctx)\n    }\n\n    #[pre_hooks(use_immediate_button)]\n    pub(crate) fn immediate_button(mut ctx: WidgetContext) -> WidgetNode {\n        button(ctx)\n    }\n\n    #[pre_hooks(use_immediate_button)]\n    pub(crate) fn immediate_tracked_button(mut ctx: WidgetContext) -> WidgetNode {\n        tracked_button(ctx)\n    }\n\n    #[pre_hooks(use_immediate_button)]\n    pub(crate) fn immediate_self_tracked_button(mut ctx: WidgetContext) -> WidgetNode {\n        self_tracked_button(ctx)\n    }\n\n    #[pre_hooks(use_immediate_text_input)]\n    pub(crate) fn immediate_text_input(mut ctx: WidgetContext) -> WidgetNode {\n        text_input(ctx)\n    }\n\n    #[pre_hooks(use_immediate_text_input, use_immediate_button)]\n    pub(crate) fn immediate_input_field(mut ctx: WidgetContext) -> WidgetNode {\n        input_field(ctx)\n    }\n\n    #[pre_hooks(use_immediate_button)]\n    pub(crate) fn immediate_slider_view(mut ctx: WidgetContext) -> WidgetNode {\n        slider_view(ctx)\n    }\n\n    pub(crate) fn immediate_button_paper(ctx: WidgetContext) -> WidgetNode {\n        button_paper_impl(make_widget!(immediate_button), ctx)\n    }\n\n    pub(crate) fn immediate_icon_button_paper(ctx: WidgetContext) -> WidgetNode {\n        icon_button_paper_impl(make_widget!(immediate_button_paper), ctx)\n    }\n\n    pub(crate) fn immediate_switch_button_paper(ctx: WidgetContext) -> WidgetNode {\n        switch_button_paper_impl(make_widget!(immediate_button_paper), ctx)\n    }\n\n    pub(crate) fn immediate_text_button_paper(ctx: WidgetContext) -> WidgetNode {\n        text_button_paper_impl(make_widget!(immediate_button_paper), ctx)\n    }\n\n    pub(crate) fn immediate_text_field_paper(ctx: WidgetContext) -> WidgetNode {\n        text_field_paper_impl(make_widget!(immediate_input_field), ctx)\n    }\n\n    pub(crate) fn immediate_slider_paper(ctx: WidgetContext) -> WidgetNode {\n        slider_paper_impl(make_widget!(immediate_slider_view), ctx)\n    }\n\n    pub(crate) fn immediate_numeric_slider_paper(ctx: WidgetContext) -> WidgetNode {\n        numeric_slider_paper_impl(make_widget!(immediate_slider_paper), ctx)\n    }\n}\n"
  },
  {
    "path": "crates/json-renderer/Cargo.toml",
    "content": "[package]\nname = \"raui-json-renderer\"\nversion = \"0.70.17\"\nauthors = [\"Patryk 'PsichiX' Budzynski <psichix@gmail.com>\"]\nedition = \"2024\"\ndescription = \"RAUI renderer for JSON format\"\nreadme = \"../../README.md\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/RAUI-labs/raui\"\nkeywords = [\"renderer\", \"agnostic\", \"ui\", \"interface\", \"gamedev\"]\ncategories = [\"gui\", \"rendering::graphics-api\"]\n\n[dependencies]\nraui-core = { path = \"../core\", version = \"0.70\" }\nserde = { version = \"1\", features = [\"derive\"] }\nserde_json = \"1\"\n"
  },
  {
    "path": "crates/json-renderer/src/lib.rs",
    "content": "use raui_core::{\n    layout::{CoordsMapping, Layout},\n    renderer::Renderer,\n    widget::unit::WidgetUnit,\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize)]\npub struct JsonRenderer {\n    #[serde(default)]\n    pub pretty: bool,\n}\n\nimpl Renderer<String, serde_json::Error> for JsonRenderer {\n    fn render(\n        &mut self,\n        tree: &WidgetUnit,\n        _: &CoordsMapping,\n        _layout: &Layout,\n    ) -> Result<String, serde_json::Error> {\n        if self.pretty {\n            serde_json::to_string_pretty(tree)\n        } else {\n            serde_json::to_string(tree)\n        }\n    }\n}\n\nimpl Renderer<serde_json::Value, serde_json::Error> for JsonRenderer {\n    fn render(\n        &mut self,\n        tree: &WidgetUnit,\n        _: &CoordsMapping,\n        _: &Layout,\n    ) -> Result<serde_json::Value, serde_json::Error> {\n        serde_json::to_value(tree)\n    }\n}\n"
  },
  {
    "path": "crates/material/Cargo.toml",
    "content": "[package]\nname = \"raui-material\"\nversion = \"0.70.17\"\nauthors = [\"Patryk 'PsichiX' Budzynski <psichix@gmail.com>\"]\nedition = \"2024\"\ndescription = \"Material components library for RAUI\"\nreadme = \"../../README.md\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/RAUI-labs/raui\"\nkeywords = [\"renderer\", \"agnostic\", \"ui\", \"interface\", \"gamedev\"]\ncategories = [\"gui\", \"rendering::graphics-api\"]\n\n[dependencies]\nraui-core = { path = \"../core\", version = \"0.70\" }\nserde = { version = \"1\", features = [\"derive\"] }\n"
  },
  {
    "path": "crates/material/src/component/containers/context_paper.rs",
    "content": "use crate::{\n    component::containers::{paper::PaperProps, wrap_paper::wrap_paper},\n    theme::{ThemeColor, ThemedWidgetProps},\n};\nuse raui_core::{\n    PropsData, Scalar, make_widget, unpack_named_slots,\n    widget::{\n        WidgetIdOrRef,\n        component::{\n            containers::{\n                context_box::portals_context_box,\n                size_box::{SizeBoxProps, size_box},\n                wrap_box::WrapBoxProps,\n            },\n            interactive::button::{ButtonNotifyProps, button},\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::{image::ImageBoxFrame, size::SizeBoxSizeValue},\n        utils::Rect,\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Clone, Serialize, Deserialize)]\n#[props_data(raui_core::props::PropsData)]\n#[prefab(raui_core::Prefab)]\npub struct ContextPaperProps {\n    #[serde(default = \"ContextPaperProps::default_margin\")]\n    pub margin: Rect,\n    #[serde(default = \"ContextPaperProps::default_frame\")]\n    pub frame: Option<Scalar>,\n    #[serde(default)]\n    pub notify_backdrop_accept: WidgetIdOrRef,\n}\n\nimpl Default for ContextPaperProps {\n    fn default() -> Self {\n        Self {\n            margin: Self::default_margin(),\n            frame: Self::default_frame(),\n            notify_backdrop_accept: Default::default(),\n        }\n    }\n}\n\nimpl ContextPaperProps {\n    fn default_margin() -> Rect {\n        Rect {\n            left: 10.0,\n            right: 10.0,\n            top: 10.0,\n            bottom: 10.0,\n        }\n    }\n\n    #[allow(clippy::unnecessary_wraps)]\n    fn default_frame() -> Option<Scalar> {\n        Some(2.0)\n    }\n}\n\npub fn context_paper(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        idref,\n        key,\n        props,\n        named_slots,\n        ..\n    } = context;\n    unpack_named_slots!(named_slots => {content, context});\n\n    let ContextPaperProps {\n        margin,\n        frame,\n        notify_backdrop_accept,\n    } = props.read_cloned_or_default();\n\n    let context_size_props = SizeBoxProps {\n        width: SizeBoxSizeValue::Content,\n        height: SizeBoxSizeValue::Content,\n        ..Default::default()\n    };\n    let themed_props = props.read_cloned_or_else(|| ThemedWidgetProps {\n        color: ThemeColor::Primary,\n        ..Default::default()\n    });\n    let paper_props = props.read_cloned_or_else(|| PaperProps {\n        frame: frame.map(|v| ImageBoxFrame::from((v, true))),\n        ..Default::default()\n    });\n    let wrap_props = props\n        .clone()\n        .with(themed_props)\n        .with(paper_props)\n        .with(WrapBoxProps {\n            margin,\n            fill: false,\n        });\n    let backdrop_size_props = SizeBoxProps {\n        width: SizeBoxSizeValue::Fill,\n        height: SizeBoxSizeValue::Fill,\n        ..Default::default()\n    };\n\n    make_widget!(portals_context_box)\n        .key(key)\n        .maybe_idref(idref.cloned())\n        .merge_props(props.clone())\n        .named_slot(\"content\", content)\n        .named_slot(\n            \"context\",\n            make_widget!(size_box)\n                .key(\"size\")\n                .with_props(context_size_props)\n                .named_slot(\n                    \"content\",\n                    make_widget!(wrap_paper)\n                        .key(\"wrap\")\n                        .merge_props(wrap_props)\n                        .named_slot(\"content\", context),\n                ),\n        )\n        .named_slot(\n            \"backdrop\",\n            make_widget!(button)\n                .key(\"button\")\n                .with_props(ButtonNotifyProps(notify_backdrop_accept))\n                .named_slot(\n                    \"content\",\n                    make_widget!(size_box)\n                        .key(\"size\")\n                        .with_props(backdrop_size_props),\n                ),\n        )\n        .into()\n}\n"
  },
  {
    "path": "crates/material/src/component/containers/flex_paper.rs",
    "content": "use crate::component::containers::paper::paper;\nuse raui_core::{\n    make_widget,\n    widget::{\n        component::containers::flex_box::{flex_box, nav_flex_box},\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::content::ContentBoxItemLayout,\n    },\n};\n\npub fn nav_flex_paper(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        idref,\n        key,\n        props,\n        listed_slots,\n        ..\n    } = context;\n\n    let inner_props = props.clone().without::<ContentBoxItemLayout>();\n\n    make_widget!(paper)\n        .key(key)\n        .maybe_idref(idref.cloned())\n        .merge_props(props.clone())\n        .listed_slot(\n            make_widget!(nav_flex_box)\n                .key(\"flex\")\n                .merge_props(inner_props)\n                .listed_slots(listed_slots),\n        )\n        .into()\n}\n\npub fn flex_paper(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        idref,\n        key,\n        props,\n        listed_slots,\n        ..\n    } = context;\n\n    let inner_props = props.clone().without::<ContentBoxItemLayout>();\n\n    make_widget!(paper)\n        .key(key)\n        .maybe_idref(idref.cloned())\n        .merge_props(props.clone())\n        .listed_slot(\n            make_widget!(flex_box)\n                .key(\"flex\")\n                .merge_props(inner_props)\n                .listed_slots(listed_slots),\n        )\n        .into()\n}\n"
  },
  {
    "path": "crates/material/src/component/containers/grid_paper.rs",
    "content": "use crate::component::containers::paper::paper;\nuse raui_core::{\n    make_widget,\n    widget::{\n        component::containers::grid_box::{grid_box, nav_grid_box},\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::content::ContentBoxItemLayout,\n    },\n};\n\npub fn nav_grid_paper(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        idref,\n        key,\n        props,\n        listed_slots,\n        ..\n    } = context;\n\n    let inner_props = props.clone().without::<ContentBoxItemLayout>();\n\n    make_widget!(paper)\n        .key(key)\n        .maybe_idref(idref.cloned())\n        .merge_props(props.clone())\n        .listed_slot(\n            make_widget!(nav_grid_box)\n                .key(\"grid\")\n                .merge_props(inner_props)\n                .listed_slots(listed_slots),\n        )\n        .into()\n}\n\npub fn grid_paper(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        idref,\n        key,\n        props,\n        listed_slots,\n        ..\n    } = context;\n\n    let inner_props = props.clone().without::<ContentBoxItemLayout>();\n\n    make_widget!(paper)\n        .key(key)\n        .maybe_idref(idref.cloned())\n        .merge_props(props.clone())\n        .listed_slot(\n            make_widget!(grid_box)\n                .key(\"grid\")\n                .merge_props(inner_props)\n                .listed_slots(listed_slots),\n        )\n        .into()\n}\n"
  },
  {
    "path": "crates/material/src/component/containers/horizontal_paper.rs",
    "content": "use crate::component::containers::paper::paper;\nuse raui_core::{\n    make_widget,\n    widget::{\n        component::containers::horizontal_box::{horizontal_box, nav_horizontal_box},\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::content::ContentBoxItemLayout,\n    },\n};\n\npub fn nav_horizontal_paper(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        idref,\n        key,\n        props,\n        listed_slots,\n        ..\n    } = context;\n\n    let inner_props = props.clone().without::<ContentBoxItemLayout>();\n\n    make_widget!(paper)\n        .key(key)\n        .maybe_idref(idref.cloned())\n        .merge_props(props.clone())\n        .listed_slot(\n            make_widget!(nav_horizontal_box)\n                .key(\"horizontal\")\n                .merge_props(inner_props)\n                .listed_slots(listed_slots),\n        )\n        .into()\n}\n\npub fn horizontal_paper(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        idref,\n        key,\n        props,\n        listed_slots,\n        ..\n    } = context;\n\n    let inner_props = props.clone().without::<ContentBoxItemLayout>();\n\n    make_widget!(paper)\n        .key(key)\n        .maybe_idref(idref.cloned())\n        .merge_props(props.clone())\n        .listed_slot(\n            make_widget!(horizontal_box)\n                .key(\"horizontal\")\n                .merge_props(inner_props)\n                .listed_slots(listed_slots),\n        )\n        .into()\n}\n"
  },
  {
    "path": "crates/material/src/component/containers/mod.rs",
    "content": "pub mod context_paper;\npub mod flex_paper;\npub mod grid_paper;\npub mod horizontal_paper;\npub mod modal_paper;\npub mod paper;\npub mod scroll_paper;\npub mod text_tooltip_paper;\npub mod tooltip_paper;\npub mod vertical_paper;\npub mod window_paper;\npub mod wrap_paper;\n"
  },
  {
    "path": "crates/material/src/component/containers/modal_paper.rs",
    "content": "use crate::theme::ThemeProps;\nuse raui_core::{\n    PropsData, make_widget, unpack_named_slots,\n    widget::{\n        component::{\n            containers::{content_box::content_box, portal_box::portal_box},\n            image_box::{ImageBoxProps, image_box},\n            interactive::navigation::navigation_barrier,\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::image::{ImageBoxColor, ImageBoxMaterial},\n        utils::Color,\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Clone, Serialize, Deserialize)]\n#[props_data(raui_core::props::PropsData)]\n#[prefab(raui_core::Prefab)]\npub struct ModalPaperProps {\n    #[serde(default = \"ModalPaperProps::default_shadow_shown\")]\n    pub shadow_shown: bool,\n    #[serde(default)]\n    pub shadow_variant: String,\n}\n\nimpl ModalPaperProps {\n    fn default_shadow_shown() -> bool {\n        true\n    }\n}\n\nimpl Default for ModalPaperProps {\n    fn default() -> Self {\n        Self {\n            shadow_shown: Self::default_shadow_shown(),\n            shadow_variant: Default::default(),\n        }\n    }\n}\n\npub fn modal_paper(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        key,\n        props,\n        shared_props,\n        named_slots,\n        ..\n    } = context;\n    unpack_named_slots!(named_slots => content);\n\n    let ModalPaperProps {\n        shadow_shown,\n        shadow_variant,\n    } = props.read_cloned_or_default();\n\n    let mut color = Color::transparent();\n    if shadow_shown\n        && let Ok(props) = shared_props.read::<ThemeProps>()\n        && let Some(c) = props.modal_shadow_variants.get(&shadow_variant)\n    {\n        color = *c;\n    }\n\n    let shadow_image_props = ImageBoxProps {\n        material: ImageBoxMaterial::Color(ImageBoxColor {\n            color,\n            ..Default::default()\n        }),\n        ..Default::default()\n    };\n\n    make_widget!(portal_box)\n        .key(key)\n        .named_slot(\n            \"content\",\n            make_widget!(content_box)\n                .key(\"container\")\n                .listed_slot(\n                    make_widget!(navigation_barrier)\n                        .key(\"shadow-barrier\")\n                        .named_slot(\n                            \"content\",\n                            make_widget!(image_box)\n                                .key(\"shadow-image\")\n                                .with_props(shadow_image_props),\n                        ),\n                )\n                .listed_slot(content),\n        )\n        .into()\n}\n"
  },
  {
    "path": "crates/material/src/component/containers/paper.rs",
    "content": "use crate::theme::{ThemeColor, ThemeProps, ThemeVariant, ThemedImageMaterial, ThemedWidgetProps};\nuse raui_core::{\n    PropsData, Scalar, make_widget,\n    props::Props,\n    widget::{\n        component::{\n            WidgetComponent,\n            containers::content_box::{content_box, nav_content_box},\n            image_box::{ImageBoxProps, image_box},\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::{\n            content::ContentBoxItemLayout,\n            image::{ImageBoxColor, ImageBoxFrame, ImageBoxImageScaling, ImageBoxMaterial},\n        },\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(raui_core::props::PropsData)]\n#[prefab(raui_core::Prefab)]\npub struct PaperProps {\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub frame: Option<ImageBoxFrame>,\n    #[serde(default)]\n    pub variant: String,\n}\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(raui_core::props::PropsData)]\n#[prefab(raui_core::Prefab)]\npub struct PaperContentLayoutProps(pub ContentBoxItemLayout);\n\npub fn nav_paper(context: WidgetContext) -> WidgetNode {\n    paper_impl(make_widget!(nav_content_box), context)\n}\n\npub fn paper(context: WidgetContext) -> WidgetNode {\n    paper_impl(make_widget!(content_box), context)\n}\n\npub fn paper_impl(component: WidgetComponent, context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        idref,\n        key,\n        props,\n        shared_props,\n        listed_slots,\n        ..\n    } = context;\n\n    let paper_props = props.read_cloned_or_default::<PaperProps>();\n    let themed_props = props.read_cloned_or_default::<ThemedWidgetProps>();\n    let listed_slots = listed_slots\n        .into_iter()\n        .map(|mut item| {\n            item.remap_props(|mut props| {\n                if let Ok(PaperContentLayoutProps(layout)) = props.consume_unwrap_cloned() {\n                    props.write(layout);\n                }\n                props\n            });\n            item\n        })\n        .collect::<Vec<_>>();\n\n    let items = match themed_props.variant {\n        ThemeVariant::ContentOnly => listed_slots,\n        ThemeVariant::Filled => {\n            let content_background = shared_props.map_or_default::<ThemeProps, _, _>(|props| {\n                props\n                    .content_backgrounds\n                    .get(&paper_props.variant)\n                    .cloned()\n                    .unwrap_or_default()\n            });\n            let background_colors = shared_props\n                .map_or_default::<ThemeProps, _, _>(|props| props.background_colors.clone());\n            let image = match content_background {\n                ThemedImageMaterial::Color => {\n                    let color = match themed_props.color {\n                        ThemeColor::Default => background_colors.main.default.main,\n                        ThemeColor::Primary => background_colors.main.primary.main,\n                        ThemeColor::Secondary => background_colors.main.secondary.main,\n                    };\n                    ImageBoxProps {\n                        material: ImageBoxMaterial::Color(ImageBoxColor {\n                            color,\n                            ..Default::default()\n                        }),\n                        ..Default::default()\n                    }\n                }\n                ThemedImageMaterial::Image(material) => ImageBoxProps {\n                    material: ImageBoxMaterial::Image(material),\n                    ..Default::default()\n                },\n                ThemedImageMaterial::Procedural(material) => ImageBoxProps {\n                    material: ImageBoxMaterial::Procedural(material),\n                    ..Default::default()\n                },\n            };\n            let props = Props::new(ContentBoxItemLayout {\n                depth: Scalar::NEG_INFINITY,\n                ..Default::default()\n            })\n            .with(image);\n            let background = make_widget!(image_box)\n                .key(\"background\")\n                .merge_props(props)\n                .into();\n            if let Some(frame) = paper_props.frame {\n                let color = match themed_props.color {\n                    ThemeColor::Default => background_colors.main.default.dark,\n                    ThemeColor::Primary => background_colors.main.primary.dark,\n                    ThemeColor::Secondary => background_colors.main.secondary.dark,\n                };\n                let props = Props::new(ContentBoxItemLayout {\n                    depth: Scalar::NEG_INFINITY,\n                    ..Default::default()\n                })\n                .with(ImageBoxProps {\n                    material: ImageBoxMaterial::Color(ImageBoxColor {\n                        color,\n                        scaling: ImageBoxImageScaling::Frame(frame),\n                    }),\n                    ..Default::default()\n                });\n                let frame = make_widget!(image_box)\n                    .key(\"frame\")\n                    .merge_props(props)\n                    .into();\n                std::iter::once(background)\n                    .chain(std::iter::once(frame))\n                    .chain(listed_slots)\n                    .collect::<Vec<_>>()\n            } else {\n                std::iter::once(background)\n                    .chain(listed_slots)\n                    .collect::<Vec<_>>()\n            }\n        }\n        ThemeVariant::Outline => {\n            if let Some(frame) = paper_props.frame {\n                let background_colors = shared_props\n                    .map_or_default::<ThemeProps, _, _>(|props| props.background_colors.clone());\n                let color = match themed_props.color {\n                    ThemeColor::Default => background_colors.main.default.dark,\n                    ThemeColor::Primary => background_colors.main.primary.dark,\n                    ThemeColor::Secondary => background_colors.main.secondary.dark,\n                };\n                let props = Props::new(ContentBoxItemLayout {\n                    depth: Scalar::NEG_INFINITY,\n                    ..Default::default()\n                })\n                .with(ImageBoxProps {\n                    material: ImageBoxMaterial::Color(ImageBoxColor {\n                        color,\n                        scaling: ImageBoxImageScaling::Frame(frame),\n                    }),\n                    ..Default::default()\n                });\n                let frame = make_widget!(image_box)\n                    .key(\"frame\")\n                    .merge_props(props)\n                    .into();\n                std::iter::once(frame)\n                    .chain(listed_slots)\n                    .collect::<Vec<_>>()\n            } else {\n                listed_slots\n            }\n        }\n    };\n\n    component\n        .key(key)\n        .maybe_idref(idref.cloned())\n        .merge_props(props.clone())\n        .listed_slots(items)\n        .into()\n}\n"
  },
  {
    "path": "crates/material/src/component/containers/scroll_paper.rs",
    "content": "use crate::{\n    component::containers::paper::paper,\n    theme::{ThemeColor, ThemeProps, ThemedImageMaterial, ThemedWidgetProps},\n};\nuse raui_core::{\n    PropsData, Scalar, make_widget, unpack_named_slots,\n    widget::{\n        component::containers::scroll_box::{\n            SideScrollbarsProps, nav_scroll_box, nav_scroll_box_side_scrollbars,\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::{\n            content::ContentBoxItemLayout,\n            image::{ImageBoxColor, ImageBoxMaterial},\n        },\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Clone, Serialize, Deserialize)]\n#[props_data(raui_core::props::PropsData)]\n#[prefab(raui_core::Prefab)]\npub struct SideScrollbarsPaperProps {\n    #[serde(default)]\n    pub size: Scalar,\n    #[serde(default)]\n    pub back_variant: Option<String>,\n    #[serde(default)]\n    pub front_variant: String,\n}\n\nimpl Default for SideScrollbarsPaperProps {\n    fn default() -> Self {\n        Self {\n            size: 10.0,\n            back_variant: None,\n            front_variant: Default::default(),\n        }\n    }\n}\n\npub fn scroll_paper(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        idref,\n        key,\n        props,\n        named_slots,\n        ..\n    } = context;\n    unpack_named_slots!(named_slots => {content, scrollbars});\n\n    let inner_props = props.clone().without::<ContentBoxItemLayout>();\n\n    make_widget!(paper)\n        .key(key)\n        .maybe_idref(idref.cloned())\n        .merge_props(props.clone())\n        .listed_slot(\n            make_widget!(nav_scroll_box)\n                .key(\"scroll\")\n                .merge_props(inner_props)\n                .named_slot(\"content\", content)\n                .named_slot(\"scrollbars\", scrollbars),\n        )\n        .into()\n}\n\npub fn scroll_paper_side_scrollbars(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        idref,\n        key,\n        props,\n        shared_props,\n        ..\n    } = context;\n\n    let scrollbars_props = props.read_cloned_or_default::<SideScrollbarsPaperProps>();\n    let themed_props = props.read_cloned_or_default::<ThemedWidgetProps>();\n    let colors =\n        shared_props.map_or_default::<ThemeProps, _, _>(|props| props.active_colors.clone());\n\n    let back_material = if let Some(back_variant) = &scrollbars_props.back_variant {\n        let background = shared_props.map_or_default::<ThemeProps, _, _>(|props| {\n            props\n                .button_backgrounds\n                .get(back_variant)\n                .cloned()\n                .unwrap_or_default()\n                .default\n        });\n        Some(match background {\n            ThemedImageMaterial::Color => {\n                let color = match themed_props.color {\n                    ThemeColor::Default => colors.main.default.main,\n                    ThemeColor::Primary => colors.main.primary.main,\n                    ThemeColor::Secondary => colors.main.secondary.main,\n                };\n                ImageBoxMaterial::Color(ImageBoxColor {\n                    color,\n                    ..Default::default()\n                })\n            }\n            ThemedImageMaterial::Image(material) => ImageBoxMaterial::Image(material),\n            ThemedImageMaterial::Procedural(material) => ImageBoxMaterial::Procedural(material),\n        })\n    } else {\n        None\n    };\n\n    let front_material = {\n        let background = shared_props.map_or_default::<ThemeProps, _, _>(|props| {\n            props\n                .button_backgrounds\n                .get(&scrollbars_props.front_variant)\n                .cloned()\n                .unwrap_or_default()\n                .trigger\n        });\n        match background {\n            ThemedImageMaterial::Color => {\n                let color = match themed_props.color {\n                    ThemeColor::Default => colors.main.default.main,\n                    ThemeColor::Primary => colors.main.primary.main,\n                    ThemeColor::Secondary => colors.main.secondary.main,\n                };\n                ImageBoxMaterial::Color(ImageBoxColor {\n                    color,\n                    ..Default::default()\n                })\n            }\n            ThemedImageMaterial::Image(material) => ImageBoxMaterial::Image(material),\n            ThemedImageMaterial::Procedural(material) => ImageBoxMaterial::Procedural(material),\n        }\n    };\n\n    props.write(SideScrollbarsProps {\n        size: scrollbars_props.size,\n        back_material,\n        front_material,\n    });\n\n    make_widget!(nav_scroll_box_side_scrollbars)\n        .key(key)\n        .maybe_idref(idref.cloned())\n        .merge_props(props.clone())\n        .into()\n}\n"
  },
  {
    "path": "crates/material/src/component/containers/text_tooltip_paper.rs",
    "content": "use crate::component::{containers::tooltip_paper::tooltip_paper, text_paper::text_paper};\nuse raui_core::{\n    make_widget, unpack_named_slots,\n    widget::{context::WidgetContext, node::WidgetNode},\n};\n\npub fn text_tooltip_paper(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        idref,\n        key,\n        props,\n        named_slots,\n        ..\n    } = context;\n    unpack_named_slots!(named_slots => content);\n\n    make_widget!(tooltip_paper)\n        .key(key)\n        .maybe_idref(idref.cloned())\n        .merge_props(props.clone())\n        .named_slot(\"content\", content)\n        .named_slot(\n            \"tooltip\",\n            make_widget!(text_paper)\n                .key(\"text\")\n                .merge_props(props.clone()),\n        )\n        .into()\n}\n"
  },
  {
    "path": "crates/material/src/component/containers/tooltip_paper.rs",
    "content": "use crate::{\n    component::containers::{paper::PaperProps, wrap_paper::wrap_paper},\n    theme::{ThemeColor, ThemedWidgetProps},\n};\nuse raui_core::{\n    PropsData, Scalar, make_widget, unpack_named_slots,\n    widget::{\n        component::containers::{\n            size_box::{SizeBoxProps, size_box},\n            tooltip_box::portals_tooltip_box,\n            wrap_box::WrapBoxProps,\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::{image::ImageBoxFrame, size::SizeBoxSizeValue},\n        utils::Rect,\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Copy, Clone, Serialize, Deserialize)]\n#[props_data(raui_core::props::PropsData)]\n#[prefab(raui_core::Prefab)]\npub struct TooltipPaperProps {\n    #[serde(default = \"TooltipPaperProps::default_margin\")]\n    pub margin: Rect,\n    #[serde(default = \"TooltipPaperProps::default_frame\")]\n    pub frame: Option<Scalar>,\n}\n\nimpl Default for TooltipPaperProps {\n    fn default() -> Self {\n        Self {\n            margin: Self::default_margin(),\n            frame: Self::default_frame(),\n        }\n    }\n}\n\nimpl TooltipPaperProps {\n    fn default_margin() -> Rect {\n        Rect {\n            left: 10.0,\n            right: 10.0,\n            top: 10.0,\n            bottom: 10.0,\n        }\n    }\n\n    #[allow(clippy::unnecessary_wraps)]\n    fn default_frame() -> Option<Scalar> {\n        Some(2.0)\n    }\n}\n\npub fn tooltip_paper(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        idref,\n        key,\n        props,\n        named_slots,\n        ..\n    } = context;\n    unpack_named_slots!(named_slots => {content, tooltip});\n\n    let TooltipPaperProps { margin, frame } = props.read_cloned_or_default();\n\n    let size_props = SizeBoxProps {\n        width: SizeBoxSizeValue::Content,\n        height: SizeBoxSizeValue::Content,\n        ..Default::default()\n    };\n    let themed_props = props.read_cloned_or_else(|| ThemedWidgetProps {\n        color: ThemeColor::Primary,\n        ..Default::default()\n    });\n    let paper_props = props.read_cloned_or_else(|| PaperProps {\n        frame: frame.map(|v| ImageBoxFrame::from((v, true))),\n        ..Default::default()\n    });\n    let wrap_props = props\n        .clone()\n        .with(themed_props)\n        .with(paper_props)\n        .with(WrapBoxProps {\n            margin,\n            fill: false,\n        });\n\n    make_widget!(portals_tooltip_box)\n        .key(key)\n        .maybe_idref(idref.cloned())\n        .merge_props(props.clone())\n        .named_slot(\"content\", content)\n        .named_slot(\n            \"tooltip\",\n            make_widget!(size_box)\n                .key(\"size\")\n                .with_props(size_props)\n                .named_slot(\n                    \"content\",\n                    make_widget!(wrap_paper)\n                        .key(\"wrap\")\n                        .merge_props(wrap_props)\n                        .named_slot(\"content\", tooltip),\n                ),\n        )\n        .into()\n}\n"
  },
  {
    "path": "crates/material/src/component/containers/vertical_paper.rs",
    "content": "use crate::component::containers::paper::paper;\nuse raui_core::{\n    make_widget,\n    widget::{\n        component::containers::vertical_box::{nav_vertical_box, vertical_box},\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::content::ContentBoxItemLayout,\n    },\n};\n\npub fn nav_vertical_paper(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        idref,\n        key,\n        props,\n        listed_slots,\n        ..\n    } = context;\n\n    let inner_props = props.clone().without::<ContentBoxItemLayout>();\n\n    make_widget!(paper)\n        .key(key)\n        .maybe_idref(idref.cloned())\n        .merge_props(props.clone())\n        .listed_slot(\n            make_widget!(nav_vertical_box)\n                .key(\"vertical\")\n                .merge_props(inner_props)\n                .listed_slots(listed_slots),\n        )\n        .into()\n}\n\npub fn vertical_paper(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        idref,\n        key,\n        props,\n        listed_slots,\n        ..\n    } = context;\n\n    let inner_props = props.clone().without::<ContentBoxItemLayout>();\n\n    make_widget!(paper)\n        .key(key)\n        .maybe_idref(idref.cloned())\n        .merge_props(props.clone())\n        .listed_slot(\n            make_widget!(vertical_box)\n                .key(\"vertical\")\n                .merge_props(inner_props)\n                .listed_slots(listed_slots),\n        )\n        .into()\n}\n"
  },
  {
    "path": "crates/material/src/component/containers/window_paper.rs",
    "content": "use crate::{\n    component::containers::wrap_paper::wrap_paper,\n    theme::{ThemeColor, ThemedWidgetProps},\n};\nuse raui_core::{\n    PropsData, Scalar, make_widget, unpack_named_slots,\n    widget::{\n        component::containers::{\n            horizontal_box::horizontal_box, vertical_box::vertical_box, wrap_box::WrapBoxProps,\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::flex::FlexBoxItemLayout,\n        utils::Rect,\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Clone, Serialize, Deserialize)]\n#[props_data(raui_core::props::PropsData)]\n#[prefab(raui_core::Prefab)]\npub struct WindowPaperProps {\n    #[serde(default)]\n    pub bar_color: ThemeColor,\n    #[serde(default = \"WindowPaperProps::default_bar_margin\")]\n    pub bar_margin: Rect,\n    #[serde(default = \"WindowPaperProps::default_bar_height\")]\n    pub bar_height: Option<Scalar>,\n    #[serde(default = \"WindowPaperProps::default_content_margin\")]\n    pub content_margin: Rect,\n}\n\nimpl Default for WindowPaperProps {\n    fn default() -> Self {\n        Self {\n            bar_color: ThemeColor::Primary,\n            bar_margin: Self::default_bar_margin(),\n            bar_height: Self::default_bar_height(),\n            content_margin: Self::default_content_margin(),\n        }\n    }\n}\n\nimpl WindowPaperProps {\n    fn default_bar_margin() -> Rect {\n        Rect {\n            left: 10.0,\n            right: 10.0,\n            top: 4.0,\n            bottom: 4.0,\n        }\n    }\n\n    fn default_bar_height() -> Option<Scalar> {\n        Some(32.0)\n    }\n\n    fn default_content_margin() -> Rect {\n        10.0.into()\n    }\n}\n\npub fn window_paper(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        idref,\n        key,\n        props,\n        named_slots,\n        ..\n    } = context;\n    unpack_named_slots!(named_slots => {content, bar});\n\n    let window_props = props.read_cloned_or_default::<WindowPaperProps>();\n\n    make_widget!(vertical_box)\n        .key(key)\n        .maybe_idref(idref.cloned())\n        .merge_props(props.clone())\n        .listed_slot(\n            make_widget!(wrap_paper)\n                .key(\"bar\")\n                .with_props(ThemedWidgetProps {\n                    color: window_props.bar_color,\n                    ..Default::default()\n                })\n                .with_props(WrapBoxProps {\n                    margin: window_props.bar_margin,\n                    ..Default::default()\n                })\n                .with_props(FlexBoxItemLayout {\n                    basis: window_props.bar_height,\n                    grow: 0.0,\n                    shrink: 0.0,\n                    ..Default::default()\n                })\n                .named_slot(\"content\", bar),\n        )\n        .listed_slot(\n            make_widget!(wrap_paper)\n                .key(\"content\")\n                .with_props(WrapBoxProps {\n                    margin: window_props.content_margin,\n                    ..Default::default()\n                })\n                .named_slot(\"content\", content),\n        )\n        .into()\n}\n\npub fn window_title_controls_paper(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        key, named_slots, ..\n    } = context;\n    unpack_named_slots!(named_slots => {content, title, controls});\n\n    let mut controls = if let WidgetNode::Tuple(nodes) = controls {\n        make_widget!(horizontal_box).listed_slots(nodes).into()\n    } else {\n        controls\n    };\n    controls.remap_props(|p| {\n        if p.has::<FlexBoxItemLayout>() {\n            p\n        } else {\n            p.with(FlexBoxItemLayout {\n                grow: 0.0,\n                shrink: 0.0,\n                ..Default::default()\n            })\n        }\n    });\n\n    make_widget!(window_paper)\n        .key(key)\n        .named_slot(\n            \"bar\",\n            make_widget!(horizontal_box)\n                .key(\"bar\")\n                .listed_slot(title)\n                .listed_slot(controls),\n        )\n        .named_slot(\"content\", content)\n        .into()\n}\n"
  },
  {
    "path": "crates/material/src/component/containers/wrap_paper.rs",
    "content": "use crate::component::containers::paper::paper;\nuse raui_core::{\n    make_widget, unpack_named_slots,\n    widget::{\n        component::containers::wrap_box::wrap_box, context::WidgetContext, node::WidgetNode,\n        unit::content::ContentBoxItemLayout,\n    },\n};\n\npub fn wrap_paper(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        idref,\n        key,\n        props,\n        named_slots,\n        ..\n    } = context;\n    unpack_named_slots!(named_slots => content);\n\n    let inner_props = props.clone().without::<ContentBoxItemLayout>();\n\n    make_widget!(paper)\n        .key(key)\n        .maybe_idref(idref.cloned())\n        .merge_props(props.clone())\n        .listed_slot(\n            make_widget!(wrap_box)\n                .key(\"wrap\")\n                .merge_props(inner_props)\n                .named_slot(\"content\", content),\n        )\n        .into()\n}\n"
  },
  {
    "path": "crates/material/src/component/icon_paper.rs",
    "content": "use crate::theme::{ThemeColor, ThemeProps, ThemedWidgetProps};\nuse raui_core::{\n    PropsData, make_widget,\n    widget::{\n        component::image_box::{ImageBoxProps, image_box},\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::image::{\n            ImageBoxAspectRatio, ImageBoxImage, ImageBoxImageScaling, ImageBoxMaterial,\n            ImageBoxSizeValue,\n        },\n        utils::{Rect, Transform},\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct IconImage {\n    #[serde(default)]\n    pub id: String,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub source_rect: Option<Rect>,\n    #[serde(default)]\n    pub scaling: ImageBoxImageScaling,\n}\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(raui_core::props::PropsData)]\n#[prefab(raui_core::Prefab)]\npub struct IconPaperProps {\n    #[serde(default)]\n    pub image: IconImage,\n    #[serde(default)]\n    pub size_level: usize,\n    #[serde(default)]\n    pub transform: Transform,\n}\n\npub fn icon_paper(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        idref,\n        key,\n        props,\n        shared_props,\n        ..\n    } = context;\n\n    let themed_props = props.read_cloned_or_default::<ThemedWidgetProps>();\n    let tint = match shared_props.read::<ThemeProps>() {\n        Ok(props) => match themed_props.color {\n            ThemeColor::Default => props.active_colors.contrast.default.main,\n            ThemeColor::Primary => props.active_colors.contrast.primary.main,\n            ThemeColor::Secondary => props.active_colors.contrast.secondary.main,\n        },\n        Err(_) => Default::default(),\n    };\n    let icon_props = props.read_cloned_or_default::<IconPaperProps>();\n    let size = match shared_props.read::<ThemeProps>() {\n        Ok(props) => props\n            .icons_level_sizes\n            .get(icon_props.size_level)\n            .copied()\n            .unwrap_or(24.0),\n        Err(_) => 24.0,\n    };\n    let IconImage {\n        id,\n        source_rect,\n        scaling,\n    } = icon_props.image;\n    let image = ImageBoxImage {\n        id,\n        source_rect,\n        scaling,\n        tint,\n    };\n    let props = ImageBoxProps {\n        width: ImageBoxSizeValue::Exact(size),\n        height: ImageBoxSizeValue::Exact(size),\n        content_keep_aspect_ratio: Some(ImageBoxAspectRatio {\n            horizontal_alignment: 0.5,\n            vertical_alignment: 0.5,\n            outside: false,\n        }),\n        material: ImageBoxMaterial::Image(image),\n        transform: icon_props.transform,\n    };\n\n    make_widget!(image_box)\n        .key(key)\n        .maybe_idref(idref.cloned())\n        .with_props(props)\n        .into()\n}\n"
  },
  {
    "path": "crates/material/src/component/interactive/button_paper.rs",
    "content": "use crate::{\n    component::containers::paper::PaperProps,\n    theme::{ThemeColor, ThemeProps, ThemeVariant, ThemedImageMaterial, ThemedWidgetProps},\n};\nuse raui_core::{\n    PropsData, Scalar, make_widget,\n    props::Props,\n    unpack_named_slots,\n    widget::{\n        component::{\n            WidgetComponent,\n            containers::content_box::content_box,\n            image_box::{ImageBoxProps, image_box},\n            interactive::button::{ButtonProps, button},\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::{\n            content::ContentBoxItemLayout,\n            image::{ImageBoxColor, ImageBoxImageScaling, ImageBoxMaterial},\n        },\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Default, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]\n#[props_data(raui_core::props::PropsData)]\n#[prefab(raui_core::Prefab)]\npub enum ButtonPaperOverrideStyle {\n    #[default]\n    None,\n    Default,\n    Selected,\n    Triggered,\n}\n\nfn button_paper_content(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        key,\n        props,\n        shared_props,\n        named_slots,\n        ..\n    } = context;\n    unpack_named_slots!(named_slots => content);\n\n    let mut button_props = props.read_cloned_or_default::<ButtonProps>();\n    let paper_props = props.read_cloned_or_default::<PaperProps>();\n    let themed_props = props.read_cloned_or_default::<ThemedWidgetProps>();\n    let override_style = props.read_cloned_or_default::<ButtonPaperOverrideStyle>();\n\n    if override_style != ButtonPaperOverrideStyle::None {\n        button_props.selected = override_style == ButtonPaperOverrideStyle::Selected;\n        button_props.trigger = override_style == ButtonPaperOverrideStyle::Triggered;\n        button_props.context = false;\n    }\n\n    let items = match themed_props.variant {\n        ThemeVariant::ContentOnly => vec![content],\n        ThemeVariant::Filled => {\n            let button_background = shared_props.map_or_default::<ThemeProps, _, _>(|props| {\n                if button_props.trigger || button_props.context {\n                    props\n                        .button_backgrounds\n                        .get(&paper_props.variant)\n                        .cloned()\n                        .unwrap_or_default()\n                        .trigger\n                } else if button_props.selected {\n                    props\n                        .button_backgrounds\n                        .get(&paper_props.variant)\n                        .cloned()\n                        .unwrap_or_default()\n                        .selected\n                } else {\n                    props\n                        .button_backgrounds\n                        .get(&paper_props.variant)\n                        .cloned()\n                        .unwrap_or_default()\n                        .default\n                }\n            });\n            let button_colors = shared_props\n                .map_or_default::<ThemeProps, _, _>(|props| props.active_colors.clone());\n            let image = match button_background {\n                ThemedImageMaterial::Color => {\n                    let color = match themed_props.color {\n                        ThemeColor::Default => button_colors.main.default.main,\n                        ThemeColor::Primary => button_colors.main.primary.main,\n                        ThemeColor::Secondary => button_colors.main.secondary.main,\n                    };\n                    ImageBoxProps {\n                        material: ImageBoxMaterial::Color(ImageBoxColor {\n                            color,\n                            ..Default::default()\n                        }),\n                        ..Default::default()\n                    }\n                }\n                ThemedImageMaterial::Image(material) => ImageBoxProps {\n                    material: ImageBoxMaterial::Image(material),\n                    ..Default::default()\n                },\n                ThemedImageMaterial::Procedural(material) => ImageBoxProps {\n                    material: ImageBoxMaterial::Procedural(material),\n                    ..Default::default()\n                },\n            };\n            let props = Props::new(ContentBoxItemLayout {\n                depth: Scalar::NEG_INFINITY,\n                ..Default::default()\n            })\n            .with(image);\n            let background = make_widget!(image_box)\n                .key(\"background\")\n                .merge_props(props)\n                .into();\n            if let Some(frame) = paper_props.frame {\n                let color = match themed_props.color {\n                    ThemeColor::Default => button_colors.main.default.dark,\n                    ThemeColor::Primary => button_colors.main.primary.dark,\n                    ThemeColor::Secondary => button_colors.main.secondary.dark,\n                };\n                let props = Props::new(ContentBoxItemLayout {\n                    depth: Scalar::NEG_INFINITY,\n                    ..Default::default()\n                })\n                .with(ImageBoxProps {\n                    material: ImageBoxMaterial::Color(ImageBoxColor {\n                        color,\n                        scaling: ImageBoxImageScaling::Frame(frame),\n                    }),\n                    ..Default::default()\n                });\n                let frame = make_widget!(image_box)\n                    .key(\"frame\")\n                    .merge_props(props)\n                    .into();\n                vec![background, frame, content]\n            } else {\n                vec![background, content]\n            }\n        }\n        ThemeVariant::Outline => {\n            if let Some(frame) = paper_props.frame {\n                let button_colors = shared_props\n                    .map_or_default::<ThemeProps, _, _>(|props| props.active_colors.clone());\n                let color = match themed_props.color {\n                    ThemeColor::Default => button_colors.main.default.dark,\n                    ThemeColor::Primary => button_colors.main.primary.dark,\n                    ThemeColor::Secondary => button_colors.main.secondary.dark,\n                };\n                let props = Props::new(ContentBoxItemLayout {\n                    depth: Scalar::NEG_INFINITY,\n                    ..Default::default()\n                })\n                .with(ImageBoxProps {\n                    material: ImageBoxMaterial::Color(ImageBoxColor {\n                        color,\n                        scaling: ImageBoxImageScaling::Frame(frame),\n                    }),\n                    ..Default::default()\n                });\n                let frame = make_widget!(image_box)\n                    .key(\"frame\")\n                    .merge_props(props)\n                    .into();\n                vec![frame, content]\n            } else {\n                vec![content]\n            }\n        }\n    };\n\n    make_widget!(content_box)\n        .key(key)\n        .merge_props(props.clone())\n        .listed_slots(items)\n        .into()\n}\n\npub fn button_paper(context: WidgetContext) -> WidgetNode {\n    button_paper_impl(make_widget!(button), context)\n}\n\npub fn button_paper_impl(component: WidgetComponent, context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        idref,\n        key,\n        props,\n        named_slots,\n        ..\n    } = context;\n    unpack_named_slots!(named_slots => content);\n\n    component\n        .key(key)\n        .maybe_idref(idref.cloned())\n        .merge_props(props.clone())\n        .named_slot(\n            \"content\",\n            make_widget!(button_paper_content)\n                .key(\"content\")\n                .merge_props(props.clone())\n                .named_slot(\"content\", content),\n        )\n        .into()\n}\n"
  },
  {
    "path": "crates/material/src/component/interactive/icon_button_paper.rs",
    "content": "use crate::component::{icon_paper::icon_paper, interactive::button_paper::button_paper};\nuse raui_core::{\n    make_widget,\n    widget::{component::WidgetComponent, context::WidgetContext, node::WidgetNode},\n};\n\npub fn icon_button_paper(context: WidgetContext) -> WidgetNode {\n    icon_button_paper_impl(make_widget!(button_paper), context)\n}\n\npub fn icon_button_paper_impl(component: WidgetComponent, context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        idref, key, props, ..\n    } = context;\n\n    component\n        .key(key)\n        .maybe_idref(idref.cloned())\n        .merge_props(props.clone())\n        .named_slot(\n            \"content\",\n            make_widget!(icon_paper)\n                .key(\"icon\")\n                .merge_props(props.clone()),\n        )\n        .into()\n}\n"
  },
  {
    "path": "crates/material/src/component/interactive/mod.rs",
    "content": "pub mod button_paper;\npub mod icon_button_paper;\npub mod slider_paper;\npub mod switch_button_paper;\npub mod text_button_paper;\npub mod text_field_paper;\n"
  },
  {
    "path": "crates/material/src/component/interactive/slider_paper.rs",
    "content": "use crate::{\n    component::text_paper::{TextPaperProps, text_paper},\n    theme::{ThemeColor, ThemeProps, ThemedImageMaterial, ThemedSliderMaterial},\n};\nuse raui_core::{\n    PropsData, make_widget, unpack_named_slots,\n    widget::{\n        component::{\n            WidgetComponent,\n            containers::content_box::content_box,\n            image_box::{ImageBoxProps, image_box},\n            interactive::slider_view::{SliderViewDirection, SliderViewProps, slider_view},\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::{\n            content::ContentBoxItemLayout,\n            image::{ImageBoxColor, ImageBoxMaterial},\n            text::TextBoxSizeValue,\n        },\n        utils::Rect,\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Clone, Serialize, Deserialize)]\n#[props_data(raui_core::props::PropsData)]\n#[prefab(raui_core::Prefab)]\npub struct SliderPaperProps {\n    #[serde(default)]\n    pub variant: String,\n    #[serde(default = \"SliderPaperProps::default_background_color\")]\n    pub background_color: ThemeColor,\n    #[serde(default = \"SliderPaperProps::default_filling_color\")]\n    pub filling_color: ThemeColor,\n}\n\nimpl Default for SliderPaperProps {\n    fn default() -> Self {\n        Self {\n            variant: Default::default(),\n            background_color: Self::default_background_color(),\n            filling_color: Self::default_filling_color(),\n        }\n    }\n}\n\nimpl SliderPaperProps {\n    fn default_background_color() -> ThemeColor {\n        ThemeColor::Secondary\n    }\n\n    fn default_filling_color() -> ThemeColor {\n        ThemeColor::Primary\n    }\n}\n\n#[derive(PropsData, Debug, Default, Clone, Copy, Serialize, Deserialize)]\n#[props_data(raui_core::props::PropsData)]\n#[prefab(raui_core::Prefab)]\npub struct NumericSliderPaperProps {\n    #[serde(default)]\n    pub fractional_digits_count: Option<usize>,\n}\n\npub fn slider_paper(context: WidgetContext) -> WidgetNode {\n    slider_paper_impl(make_widget!(slider_view), context)\n}\n\npub fn slider_paper_impl(component: WidgetComponent, context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        idref,\n        key,\n        props,\n        shared_props,\n        named_slots,\n        ..\n    } = context;\n    unpack_named_slots!(named_slots => content);\n\n    let SliderPaperProps {\n        variant,\n        background_color,\n        filling_color,\n    } = props.read_cloned_or_default();\n    let anchors = props\n        .read::<SliderViewProps>()\n        .ok()\n        .map(|props| {\n            let percentage = props.get_percentage();\n            match props.direction {\n                SliderViewDirection::LeftToRight => Rect {\n                    left: 0.0,\n                    right: percentage,\n                    top: 0.0,\n                    bottom: 1.0,\n                },\n                SliderViewDirection::RightToLeft => Rect {\n                    left: 1.0 - percentage,\n                    right: 1.0,\n                    top: 0.0,\n                    bottom: 1.0,\n                },\n                SliderViewDirection::TopToBottom => Rect {\n                    left: 0.0,\n                    right: 1.0,\n                    top: 0.0,\n                    bottom: percentage,\n                },\n                SliderViewDirection::BottomToTop => Rect {\n                    left: 0.0,\n                    right: 1.0,\n                    top: 1.0 - percentage,\n                    bottom: 1.0,\n                },\n            }\n        })\n        .unwrap_or_default();\n    let (background, filling) = match shared_props.read::<ThemeProps>() {\n        Ok(props) => {\n            if let Some(material) = props.slider_variants.get(&variant).cloned() {\n                let background_color = match background_color {\n                    ThemeColor::Default => props.active_colors.main.default.main,\n                    ThemeColor::Primary => props.active_colors.main.primary.main,\n                    ThemeColor::Secondary => props.active_colors.main.secondary.main,\n                };\n                let filling_color = match filling_color {\n                    ThemeColor::Default => props.active_colors.main.default.main,\n                    ThemeColor::Primary => props.active_colors.main.primary.main,\n                    ThemeColor::Secondary => props.active_colors.main.secondary.main,\n                };\n                let ThemedSliderMaterial {\n                    background,\n                    filling,\n                } = material;\n                let background = match background {\n                    ThemedImageMaterial::Color => ImageBoxProps {\n                        material: ImageBoxMaterial::Color(ImageBoxColor {\n                            color: background_color,\n                            ..Default::default()\n                        }),\n                        ..Default::default()\n                    },\n                    ThemedImageMaterial::Image(mut data) => {\n                        data.tint = filling_color;\n                        ImageBoxProps {\n                            material: ImageBoxMaterial::Image(data),\n                            ..Default::default()\n                        }\n                    }\n                    ThemedImageMaterial::Procedural(data) => ImageBoxProps {\n                        material: ImageBoxMaterial::Procedural(data),\n                        ..Default::default()\n                    },\n                };\n                let filling = match filling {\n                    ThemedImageMaterial::Color => ImageBoxProps {\n                        material: ImageBoxMaterial::Color(ImageBoxColor {\n                            color: filling_color,\n                            ..Default::default()\n                        }),\n                        ..Default::default()\n                    },\n                    ThemedImageMaterial::Image(mut data) => {\n                        data.tint = filling_color;\n                        ImageBoxProps {\n                            material: ImageBoxMaterial::Image(data),\n                            ..Default::default()\n                        }\n                    }\n                    ThemedImageMaterial::Procedural(data) => ImageBoxProps {\n                        material: ImageBoxMaterial::Procedural(data),\n                        ..Default::default()\n                    },\n                };\n                (background, filling)\n            } else {\n                Default::default()\n            }\n        }\n        Err(_) => Default::default(),\n    };\n\n    component\n        .key(key)\n        .maybe_idref(idref.cloned())\n        .merge_props(props.clone())\n        .named_slot(\n            \"content\",\n            make_widget!(content_box)\n                .key(\"content\")\n                .merge_props(props.clone())\n                .listed_slot(\n                    make_widget!(image_box)\n                        .key(\"background\")\n                        .with_props(background),\n                )\n                .listed_slot(\n                    make_widget!(image_box)\n                        .key(\"filling\")\n                        .with_props(ContentBoxItemLayout {\n                            anchors,\n                            ..Default::default()\n                        })\n                        .with_props(filling),\n                )\n                .listed_slot(content),\n        )\n        .into()\n}\n\npub fn numeric_slider_paper(context: WidgetContext) -> WidgetNode {\n    numeric_slider_paper_impl(make_widget!(slider_paper), context)\n}\n\npub fn numeric_slider_paper_impl(component: WidgetComponent, context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        idref, key, props, ..\n    } = context;\n\n    let mut text = props.read_cloned_or_default::<TextPaperProps>();\n    text.width = TextBoxSizeValue::Fill;\n    text.height = TextBoxSizeValue::Fill;\n    let value = props\n        .read::<SliderViewProps>()\n        .ok()\n        .map(|props| props.get_value())\n        .unwrap_or_default();\n    text.text = if let Some(count) = props\n        .read_cloned_or_default::<NumericSliderPaperProps>()\n        .fractional_digits_count\n    {\n        format!(\"{value:.count$}\")\n    } else {\n        value.to_string()\n    };\n\n    component\n        .key(key)\n        .maybe_idref(idref.cloned())\n        .merge_props(props.clone())\n        .named_slot(\n            \"content\",\n            make_widget!(text_paper)\n                .merge_props(props.clone())\n                .with_props(text),\n        )\n        .into()\n}\n"
  },
  {
    "path": "crates/material/src/component/interactive/switch_button_paper.rs",
    "content": "use crate::component::{interactive::button_paper::button_paper, switch_paper::switch_paper};\nuse raui_core::{\n    make_widget,\n    widget::{component::WidgetComponent, context::WidgetContext, node::WidgetNode},\n};\n\npub fn switch_button_paper(context: WidgetContext) -> WidgetNode {\n    switch_button_paper_impl(make_widget!(button_paper), context)\n}\n\npub fn switch_button_paper_impl(component: WidgetComponent, context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        idref, key, props, ..\n    } = context;\n\n    component\n        .key(key)\n        .maybe_idref(idref.cloned())\n        .merge_props(props.clone())\n        .named_slot(\n            \"content\",\n            make_widget!(switch_paper)\n                .key(\"switch\")\n                .merge_props(props.clone()),\n        )\n        .into()\n}\n"
  },
  {
    "path": "crates/material/src/component/interactive/text_button_paper.rs",
    "content": "use crate::component::{interactive::button_paper::button_paper, text_paper::text_paper};\nuse raui_core::{\n    make_widget,\n    widget::{\n        component::{\n            WidgetComponent,\n            containers::wrap_box::{WrapBoxProps, wrap_box},\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n    },\n};\n\npub fn text_button_paper(context: WidgetContext) -> WidgetNode {\n    text_button_paper_impl(make_widget!(button_paper), context)\n}\n\npub fn text_button_paper_impl(component: WidgetComponent, context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        idref, key, props, ..\n    } = context;\n\n    let wrap_props = props.read_cloned_or_default::<WrapBoxProps>();\n\n    component\n        .key(key)\n        .maybe_idref(idref.cloned())\n        .merge_props(props.clone())\n        .named_slot(\n            \"content\",\n            make_widget!(wrap_box)\n                .key(\"wrap\")\n                .with_props(wrap_props)\n                .named_slot(\n                    \"content\",\n                    make_widget!(text_paper)\n                        .key(\"switch\")\n                        .merge_props(props.clone()),\n                ),\n        )\n        .into()\n}\n"
  },
  {
    "path": "crates/material/src/component/interactive/text_field_paper.rs",
    "content": "use crate::{\n    component::{\n        containers::paper::{PaperProps, paper},\n        text_paper::{TextPaperProps, text_paper},\n    },\n    theme::ThemedWidgetProps,\n};\nuse raui_core::{\n    PropsData, Scalar, make_widget,\n    widget::{\n        component::{\n            WidgetAlpha, WidgetComponent,\n            interactive::input_field::{\n                TextInputProps, TextInputState, input_field, input_text_with_cursor,\n            },\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::{\n            content::ContentBoxItemLayout,\n            text::{TextBoxHorizontalAlign, TextBoxSizeValue, TextBoxVerticalAlign},\n        },\n        utils::{Color, Rect, Transform},\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Clone, Serialize, Deserialize)]\n#[props_data(raui_core::props::PropsData)]\n#[prefab(raui_core::Prefab)]\npub struct TextFieldPaperProps {\n    #[serde(default)]\n    pub hint: String,\n    #[serde(default)]\n    pub width: TextBoxSizeValue,\n    #[serde(default)]\n    pub height: TextBoxSizeValue,\n    #[serde(default)]\n    pub variant: String,\n    #[serde(default)]\n    pub use_main_color: bool,\n    #[serde(default = \"TextFieldPaperProps::default_inactive_alpha\")]\n    pub inactive_alpha: Scalar,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub horizontal_align_override: Option<TextBoxHorizontalAlign>,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub vertical_align_override: Option<TextBoxVerticalAlign>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub color_override: Option<Color>,\n    #[serde(default)]\n    pub transform: Transform,\n    #[serde(default)]\n    pub paper_theme: ThemedWidgetProps,\n    #[serde(default = \"TextFieldPaperProps::default_padding\")]\n    pub padding: Rect,\n    #[serde(default)]\n    pub password: Option<char>,\n    #[serde(default = \"TextFieldPaperProps::default_cursor\")]\n    pub cursor: Option<char>,\n}\n\nimpl TextFieldPaperProps {\n    fn default_inactive_alpha() -> Scalar {\n        0.75\n    }\n\n    fn default_cursor() -> Option<char> {\n        Some('|')\n    }\n\n    fn default_padding() -> Rect {\n        4.0.into()\n    }\n}\n\nimpl Default for TextFieldPaperProps {\n    fn default() -> Self {\n        Self {\n            hint: Default::default(),\n            width: Default::default(),\n            height: Default::default(),\n            variant: Default::default(),\n            use_main_color: Default::default(),\n            inactive_alpha: Self::default_inactive_alpha(),\n            horizontal_align_override: Default::default(),\n            vertical_align_override: Default::default(),\n            color_override: Default::default(),\n            transform: Default::default(),\n            paper_theme: Default::default(),\n            padding: Self::default_padding(),\n            password: Default::default(),\n            cursor: Self::default_cursor(),\n        }\n    }\n}\n\nfn text_field_paper_content(context: WidgetContext) -> WidgetNode {\n    let WidgetContext { key, props, .. } = context;\n\n    let TextFieldPaperProps {\n        hint,\n        width,\n        height,\n        variant,\n        use_main_color,\n        inactive_alpha,\n        horizontal_align_override,\n        vertical_align_override,\n        color_override,\n        transform,\n        paper_theme,\n        padding,\n        password,\n        cursor,\n    } = props.read_cloned_or_default();\n    let TextInputState {\n        cursor_position,\n        focused,\n    } = props.read_cloned_or_default();\n    let text = props\n        .read::<TextInputProps>()\n        .ok()\n        .and_then(|props| props.text.as_ref())\n        .map(|text| text.get())\n        .unwrap_or_default();\n    let text = if let Some(c) = password {\n        std::iter::repeat_n(c, text.chars().count()).collect()\n    } else {\n        text\n    };\n    let text = if focused {\n        if let Some(cursor) = cursor {\n            input_text_with_cursor(&text, cursor_position, cursor)\n        } else {\n            text\n        }\n    } else if text.is_empty() {\n        hint\n    } else {\n        text\n    };\n    let paper_variant = props.map_or_default::<PaperProps, _, _>(|p| p.variant.clone());\n    let paper_props = props\n        .clone()\n        .with(PaperProps {\n            variant: paper_variant,\n            ..Default::default()\n        })\n        .with(paper_theme);\n    let text_props = props\n        .clone()\n        .with(TextPaperProps {\n            text,\n            width,\n            height,\n            variant,\n            use_main_color,\n            horizontal_align_override,\n            vertical_align_override,\n            color_override,\n            transform,\n        })\n        .with(ContentBoxItemLayout {\n            margin: padding,\n            ..Default::default()\n        });\n    let alpha = if focused { 1.0 } else { inactive_alpha };\n\n    make_widget!(paper)\n        .key(key)\n        .merge_props(paper_props)\n        .listed_slot(\n            make_widget!(text_paper)\n                .key(\"text\")\n                .merge_props(text_props)\n                .with_shared_props(WidgetAlpha(alpha)),\n        )\n        .into()\n}\n\npub fn text_field_paper(context: WidgetContext) -> WidgetNode {\n    text_field_paper_impl(make_widget!(input_field), context)\n}\n\npub fn text_field_paper_impl(component: WidgetComponent, context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        idref, key, props, ..\n    } = context;\n\n    component\n        .key(key)\n        .maybe_idref(idref.cloned())\n        .merge_props(props.clone())\n        .named_slot(\n            \"content\",\n            make_widget!(text_field_paper_content)\n                .key(\"text\")\n                .merge_props(props.clone()),\n        )\n        .into()\n}\n"
  },
  {
    "path": "crates/material/src/component/mod.rs",
    "content": "pub mod containers;\npub mod icon_paper;\npub mod interactive;\npub mod switch_paper;\npub mod text_paper;\n"
  },
  {
    "path": "crates/material/src/component/switch_paper.rs",
    "content": "use crate::theme::{ThemeColor, ThemeProps, ThemedImageMaterial, ThemedWidgetProps};\nuse raui_core::{\n    PropsData, Scalar, make_widget,\n    widget::{\n        component::image_box::{ImageBoxProps, image_box},\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::image::{ImageBoxColor, ImageBoxImageScaling, ImageBoxMaterial, ImageBoxSizeValue},\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(raui_core::props::PropsData)]\n#[prefab(raui_core::Prefab)]\npub struct SwitchPaperProps {\n    #[serde(default)]\n    pub on: bool,\n    #[serde(default)]\n    pub variant: String,\n    #[serde(default)]\n    pub size_level: usize,\n}\n\npub fn switch_paper(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        idref,\n        key,\n        props,\n        shared_props,\n        ..\n    } = context;\n\n    let SwitchPaperProps {\n        on,\n        variant,\n        size_level,\n    } = props.read_cloned_or_default();\n    let themed_props = props.read_cloned_or_default::<ThemedWidgetProps>();\n    let color = match shared_props.read::<ThemeProps>() {\n        Ok(props) => match themed_props.color {\n            ThemeColor::Default => props.active_colors.main.default.main,\n            ThemeColor::Primary => props.active_colors.main.primary.main,\n            ThemeColor::Secondary => props.active_colors.main.secondary.main,\n        },\n        Err(_) => Default::default(),\n    };\n    let (size, material) = match shared_props.read::<ThemeProps>() {\n        Ok(props) => {\n            let size = props\n                .icons_level_sizes\n                .get(size_level)\n                .copied()\n                .unwrap_or(24.0);\n            let material = if let Some(material) = props.switch_variants.get(&variant) {\n                if on {\n                    material.on.clone()\n                } else {\n                    material.off.clone()\n                }\n            } else {\n                Default::default()\n            };\n            (size, material)\n        }\n        Err(_) => (24.0, Default::default()),\n    };\n    let image = match material {\n        ThemedImageMaterial::Color => ImageBoxProps {\n            material: ImageBoxMaterial::Color(ImageBoxColor {\n                color,\n                scaling: if on {\n                    ImageBoxImageScaling::Stretch\n                } else {\n                    ImageBoxImageScaling::Frame((size_level as Scalar, true).into())\n                },\n            }),\n            width: ImageBoxSizeValue::Exact(size),\n            height: ImageBoxSizeValue::Exact(size),\n            ..Default::default()\n        },\n        ThemedImageMaterial::Image(mut data) => {\n            data.tint = color;\n            ImageBoxProps {\n                material: ImageBoxMaterial::Image(data),\n                width: ImageBoxSizeValue::Exact(size),\n                height: ImageBoxSizeValue::Exact(size),\n                ..Default::default()\n            }\n        }\n        ThemedImageMaterial::Procedural(data) => ImageBoxProps {\n            material: ImageBoxMaterial::Procedural(data),\n            width: ImageBoxSizeValue::Exact(size),\n            height: ImageBoxSizeValue::Exact(size),\n            ..Default::default()\n        },\n    };\n\n    make_widget!(image_box)\n        .key(key)\n        .maybe_idref(idref.cloned())\n        .with_props(image)\n        .into()\n}\n"
  },
  {
    "path": "crates/material/src/component/text_paper.rs",
    "content": "use crate::theme::{ThemeColor, ThemeProps, ThemedTextMaterial, ThemedWidgetProps};\nuse raui_core::{\n    PropsData, make_widget,\n    widget::{\n        component::text_box::{TextBoxProps, text_box},\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::text::{TextBoxHorizontalAlign, TextBoxSizeValue, TextBoxVerticalAlign},\n        utils::{Color, Transform},\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(raui_core::props::PropsData)]\n#[prefab(raui_core::Prefab)]\npub struct TextPaperProps {\n    #[serde(default)]\n    pub text: String,\n    #[serde(default)]\n    pub width: TextBoxSizeValue,\n    #[serde(default)]\n    pub height: TextBoxSizeValue,\n    #[serde(default)]\n    pub variant: String,\n    #[serde(default)]\n    pub use_main_color: bool,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub horizontal_align_override: Option<TextBoxHorizontalAlign>,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub vertical_align_override: Option<TextBoxVerticalAlign>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub color_override: Option<Color>,\n    #[serde(default)]\n    pub transform: Transform,\n}\n\npub fn text_paper(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        idref,\n        key,\n        props,\n        shared_props,\n        ..\n    } = context;\n\n    let TextPaperProps {\n        text,\n        width,\n        height,\n        variant,\n        use_main_color,\n        horizontal_align_override,\n        vertical_align_override,\n        color_override,\n        transform,\n    } = props.read_cloned_or_default();\n    let themed_props = props.read_cloned_or_default::<ThemedWidgetProps>();\n    let ThemedTextMaterial {\n        mut horizontal_align,\n        mut vertical_align,\n        direction,\n        font,\n    } = match shared_props.read::<ThemeProps>() {\n        Ok(props) => props\n            .text_variants\n            .get(&variant)\n            .cloned()\n            .unwrap_or_default(),\n        Err(_) => Default::default(),\n    };\n    if let Some(horizontal_override) = horizontal_align_override {\n        horizontal_align = horizontal_override;\n    }\n    if let Some(alignment_override) = vertical_align_override {\n        vertical_align = alignment_override;\n    }\n    let color = if let Some(color_override) = color_override {\n        color_override\n    } else {\n        match shared_props.read::<ThemeProps>() {\n            Ok(props) => {\n                if use_main_color {\n                    match themed_props.color {\n                        ThemeColor::Default => props.active_colors.main.default.main,\n                        ThemeColor::Primary => props.active_colors.main.primary.main,\n                        ThemeColor::Secondary => props.active_colors.main.secondary.main,\n                    }\n                } else {\n                    match themed_props.color {\n                        ThemeColor::Default => props.active_colors.contrast.default.main,\n                        ThemeColor::Primary => props.active_colors.contrast.primary.main,\n                        ThemeColor::Secondary => props.active_colors.contrast.secondary.main,\n                    }\n                }\n            }\n            Err(_) => Default::default(),\n        }\n    };\n    let props = TextBoxProps {\n        text,\n        width,\n        height,\n        horizontal_align,\n        vertical_align,\n        direction,\n        font,\n        color,\n        transform,\n    };\n\n    make_widget!(text_box)\n        .key(key)\n        .maybe_idref(idref.cloned())\n        .with_props(props)\n        .into()\n}\n"
  },
  {
    "path": "crates/material/src/lib.rs",
    "content": "//! Theme-able RAUI components\n\npub mod component;\npub mod theme;\n\nuse raui_core::{application::Application, widget::FnWidget};\n\npub fn setup(app: &mut Application) {\n    app.register_props::<component::containers::context_paper::ContextPaperProps>(\n        \"ContextPaperProps\",\n    );\n    app.register_props::<component::containers::modal_paper::ModalPaperProps>(\"ModalPaperProps\");\n    app.register_props::<component::containers::paper::PaperProps>(\"PaperProps\");\n    app.register_props::<component::containers::paper::PaperContentLayoutProps>(\n        \"PaperContentLayoutProps\",\n    );\n    app.register_props::<component::containers::tooltip_paper::TooltipPaperProps>(\n        \"TooltipPaperProps\",\n    );\n    app.register_props::<component::containers::scroll_paper::SideScrollbarsPaperProps>(\n        \"SideScrollbarsPaperProps\",\n    );\n    app.register_props::<component::containers::window_paper::WindowPaperProps>(\"WindowPaperProps\");\n    app.register_props::<component::icon_paper::IconPaperProps>(\"IconPaperProps\");\n    app.register_props::<component::interactive::button_paper::ButtonPaperOverrideStyle>(\n        \"ButtonPaperOverrideStyle\",\n    );\n    app.register_props::<component::interactive::slider_paper::SliderPaperProps>(\n        \"SliderPaperProps\",\n    );\n    app.register_props::<component::interactive::slider_paper::NumericSliderPaperProps>(\n        \"NumericSliderPaperProps\",\n    );\n    app.register_props::<component::interactive::text_field_paper::TextFieldPaperProps>(\n        \"TextFieldPaperProps\",\n    );\n    app.register_props::<component::switch_paper::SwitchPaperProps>(\"SwitchPaperProps\");\n    app.register_props::<component::text_paper::TextPaperProps>(\"TextPaperProps\");\n    app.register_props::<theme::ThemedWidgetProps>(\"ThemedWidgetProps\");\n    app.register_props::<theme::ThemeProps>(\"ThemeProps\");\n\n    app.register_component(\n        \"context_paper\",\n        FnWidget::pointer(component::containers::context_paper::context_paper),\n    );\n    app.register_component(\n        \"nav_flex_paper\",\n        FnWidget::pointer(component::containers::flex_paper::nav_flex_paper),\n    );\n    app.register_component(\n        \"flex_paper\",\n        FnWidget::pointer(component::containers::flex_paper::flex_paper),\n    );\n    app.register_component(\n        \"nav_grid_paper\",\n        FnWidget::pointer(component::containers::grid_paper::nav_grid_paper),\n    );\n    app.register_component(\n        \"grid_paper\",\n        FnWidget::pointer(component::containers::grid_paper::grid_paper),\n    );\n    app.register_component(\n        \"nav_horizontal_paper\",\n        FnWidget::pointer(component::containers::horizontal_paper::nav_horizontal_paper),\n    );\n    app.register_component(\n        \"horizontal_paper\",\n        FnWidget::pointer(component::containers::horizontal_paper::horizontal_paper),\n    );\n    app.register_component(\n        \"modal_paper\",\n        FnWidget::pointer(component::containers::modal_paper::modal_paper),\n    );\n    app.register_component(\n        \"paper\",\n        FnWidget::pointer(component::containers::paper::paper),\n    );\n    app.register_component(\n        \"scroll_paper\",\n        FnWidget::pointer(component::containers::scroll_paper::scroll_paper),\n    );\n    app.register_component(\n        \"scroll_paper_side_scrollbars\",\n        FnWidget::pointer(component::containers::scroll_paper::scroll_paper_side_scrollbars),\n    );\n    app.register_component(\n        \"text_tooltip_paper\",\n        FnWidget::pointer(component::containers::text_tooltip_paper::text_tooltip_paper),\n    );\n    app.register_component(\n        \"tooltip_paper\",\n        FnWidget::pointer(component::containers::tooltip_paper::tooltip_paper),\n    );\n    app.register_component(\n        \"nav_vertical_paper\",\n        FnWidget::pointer(component::containers::vertical_paper::nav_vertical_paper),\n    );\n    app.register_component(\n        \"vertical_paper\",\n        FnWidget::pointer(component::containers::vertical_paper::vertical_paper),\n    );\n    app.register_component(\n        \"window_paper\",\n        FnWidget::pointer(component::containers::window_paper::window_paper),\n    );\n    app.register_component(\n        \"window_title_controls_paper\",\n        FnWidget::pointer(component::containers::window_paper::window_title_controls_paper),\n    );\n    app.register_component(\n        \"wrap_paper\",\n        FnWidget::pointer(component::containers::wrap_paper::wrap_paper),\n    );\n    app.register_component(\n        \"icon_paper\",\n        FnWidget::pointer(component::icon_paper::icon_paper),\n    );\n    app.register_component(\n        \"button_paper\",\n        FnWidget::pointer(component::interactive::button_paper::button_paper),\n    );\n    app.register_component(\n        \"icon_button_paper\",\n        FnWidget::pointer(component::interactive::icon_button_paper::icon_button_paper),\n    );\n    app.register_component(\n        \"slider_paper\",\n        FnWidget::pointer(component::interactive::slider_paper::slider_paper),\n    );\n    app.register_component(\n        \"numeric_slider_paper\",\n        FnWidget::pointer(component::interactive::slider_paper::numeric_slider_paper),\n    );\n    app.register_component(\n        \"switch_button_paper\",\n        FnWidget::pointer(component::interactive::switch_button_paper::switch_button_paper),\n    );\n    app.register_component(\n        \"text_button_paper\",\n        FnWidget::pointer(component::interactive::text_button_paper::text_button_paper),\n    );\n    app.register_component(\n        \"text_field_paper\",\n        FnWidget::pointer(component::interactive::text_field_paper::text_field_paper),\n    );\n    app.register_component(\n        \"switch_paper\",\n        FnWidget::pointer(component::switch_paper::switch_paper),\n    );\n    app.register_component(\n        \"text_paper\",\n        FnWidget::pointer(component::text_paper::text_paper),\n    );\n}\n"
  },
  {
    "path": "crates/material/src/theme.rs",
    "content": "use raui_core::{\n    widget::{\n        unit::{\n            image::{ImageBoxImage, ImageBoxProcedural},\n            text::{TextBoxDirection, TextBoxFont, TextBoxHorizontalAlign, TextBoxVerticalAlign},\n        },\n        utils::{Color, lerp_clamped},\n    },\n    {PropsData, Scalar},\n};\nuse serde::{Deserialize, Serialize};\nuse std::collections::HashMap;\nuse std::f32::consts::PI;\n\nconst DEFAULT_BACKGROUND_MIXING_FACTOR: Scalar = 0.1;\nconst DEFAULT_VARIANT_MIXING_FACTOR: Scalar = 0.2;\n\n#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]\npub enum ThemeColor {\n    #[default]\n    Default,\n    Primary,\n    Secondary,\n}\n\n#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]\npub enum ThemeColorVariant {\n    #[default]\n    Main,\n    Light,\n    Dark,\n}\n\n#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]\npub enum ThemeVariant {\n    ContentOnly,\n    #[default]\n    Filled,\n    Outline,\n}\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(raui_core::props::PropsData)]\n#[prefab(raui_core::Prefab)]\npub struct ThemedWidgetProps {\n    #[serde(default)]\n    pub color: ThemeColor,\n    #[serde(default)]\n    pub color_variant: ThemeColorVariant,\n    #[serde(default)]\n    pub variant: ThemeVariant,\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct ThemeColorSet {\n    #[serde(default)]\n    pub main: Color,\n    #[serde(default)]\n    pub light: Color,\n    #[serde(default)]\n    pub dark: Color,\n}\n\nimpl ThemeColorSet {\n    pub fn uniform(color: Color) -> Self {\n        Self {\n            main: color,\n            light: color,\n            dark: color,\n        }\n    }\n\n    pub fn get(&self, variant: ThemeColorVariant) -> Color {\n        match variant {\n            ThemeColorVariant::Main => self.main,\n            ThemeColorVariant::Light => self.light,\n            ThemeColorVariant::Dark => self.dark,\n        }\n    }\n\n    pub fn get_themed(&self, themed: &ThemedWidgetProps) -> Color {\n        self.get(themed.color_variant)\n    }\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct ThemeColors {\n    #[serde(default)]\n    pub default: ThemeColorSet,\n    #[serde(default)]\n    pub primary: ThemeColorSet,\n    #[serde(default)]\n    pub secondary: ThemeColorSet,\n}\n\nimpl ThemeColors {\n    pub fn uniform(set: ThemeColorSet) -> Self {\n        Self {\n            default: set.to_owned(),\n            primary: set.to_owned(),\n            secondary: set,\n        }\n    }\n\n    pub fn get(&self, color: ThemeColor, variant: ThemeColorVariant) -> Color {\n        match color {\n            ThemeColor::Default => self.default.get(variant),\n            ThemeColor::Primary => self.primary.get(variant),\n            ThemeColor::Secondary => self.secondary.get(variant),\n        }\n    }\n\n    pub fn get_themed(&self, themed: &ThemedWidgetProps) -> Color {\n        self.get(themed.color, themed.color_variant)\n    }\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct ThemeColorsBundle {\n    #[serde(default)]\n    pub main: ThemeColors,\n    #[serde(default)]\n    pub contrast: ThemeColors,\n}\n\nimpl ThemeColorsBundle {\n    pub fn uniform(colors: ThemeColors) -> Self {\n        Self {\n            main: colors.to_owned(),\n            contrast: colors,\n        }\n    }\n\n    pub fn get(&self, use_main: bool, color: ThemeColor, variant: ThemeColorVariant) -> Color {\n        if use_main {\n            self.main.get(color, variant)\n        } else {\n            self.contrast.get(color, variant)\n        }\n    }\n\n    pub fn get_themed(&self, use_main: bool, themed: &ThemedWidgetProps) -> Color {\n        self.get(use_main, themed.color, themed.color_variant)\n    }\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub enum ThemedImageMaterial {\n    #[default]\n    Color,\n    Image(ImageBoxImage),\n    Procedural(ImageBoxProcedural),\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct ThemedTextMaterial {\n    #[serde(default)]\n    pub horizontal_align: TextBoxHorizontalAlign,\n    #[serde(default)]\n    pub vertical_align: TextBoxVerticalAlign,\n    #[serde(default)]\n    pub direction: TextBoxDirection,\n    #[serde(default)]\n    pub font: TextBoxFont,\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct ThemedButtonMaterial {\n    #[serde(default)]\n    pub default: ThemedImageMaterial,\n    #[serde(default)]\n    pub selected: ThemedImageMaterial,\n    #[serde(default)]\n    pub trigger: ThemedImageMaterial,\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct ThemedSwitchMaterial {\n    #[serde(default)]\n    pub on: ThemedImageMaterial,\n    #[serde(default)]\n    pub off: ThemedImageMaterial,\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\npub struct ThemedSliderMaterial {\n    #[serde(default)]\n    pub background: ThemedImageMaterial,\n    #[serde(default)]\n    pub filling: ThemedImageMaterial,\n}\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\n#[props_data(raui_core::props::PropsData)]\n#[prefab(raui_core::Prefab)]\npub struct ThemeProps {\n    #[serde(default)]\n    pub active_colors: ThemeColorsBundle,\n    #[serde(default)]\n    pub background_colors: ThemeColorsBundle,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"HashMap::is_empty\")]\n    pub content_backgrounds: HashMap<String, ThemedImageMaterial>,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"HashMap::is_empty\")]\n    pub button_backgrounds: HashMap<String, ThemedButtonMaterial>,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"Vec::is_empty\")]\n    pub icons_level_sizes: Vec<Scalar>,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"HashMap::is_empty\")]\n    pub text_variants: HashMap<String, ThemedTextMaterial>,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"HashMap::is_empty\")]\n    pub switch_variants: HashMap<String, ThemedSwitchMaterial>,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"HashMap::is_empty\")]\n    pub slider_variants: HashMap<String, ThemedSliderMaterial>,\n    #[serde(default)]\n    #[serde(skip_serializing_if = \"HashMap::is_empty\")]\n    pub modal_shadow_variants: HashMap<String, Color>,\n}\n\nimpl ThemeProps {\n    pub fn active_colors(mut self, bundle: ThemeColorsBundle) -> Self {\n        self.active_colors = bundle;\n        self\n    }\n\n    pub fn background_colors(mut self, bundle: ThemeColorsBundle) -> Self {\n        self.background_colors = bundle;\n        self\n    }\n\n    pub fn content_background(mut self, id: impl ToString, material: ThemedImageMaterial) -> Self {\n        self.content_backgrounds.insert(id.to_string(), material);\n        self\n    }\n\n    pub fn button_background(mut self, id: impl ToString, material: ThemedButtonMaterial) -> Self {\n        self.button_backgrounds.insert(id.to_string(), material);\n        self\n    }\n\n    pub fn icons_level_size(mut self, level: usize, size: Scalar) -> Self {\n        self.icons_level_sizes.insert(level, size);\n        self\n    }\n\n    pub fn text_variant(mut self, id: impl ToString, material: ThemedTextMaterial) -> Self {\n        self.text_variants.insert(id.to_string(), material);\n        self\n    }\n\n    pub fn switch_variant(mut self, id: impl ToString, material: ThemedSwitchMaterial) -> Self {\n        self.switch_variants.insert(id.to_string(), material);\n        self\n    }\n\n    pub fn slider_variant(mut self, id: impl ToString, material: ThemedSliderMaterial) -> Self {\n        self.slider_variants.insert(id.to_string(), material);\n        self\n    }\n\n    pub fn modal_shadow_variant(mut self, id: impl ToString, color: Color) -> Self {\n        self.modal_shadow_variants.insert(id.to_string(), color);\n        self\n    }\n}\n\npub fn new_light_theme() -> ThemeProps {\n    new_light_theme_parameterized(\n        DEFAULT_BACKGROUND_MIXING_FACTOR,\n        DEFAULT_VARIANT_MIXING_FACTOR,\n    )\n}\n\npub fn new_light_theme_parameterized(\n    background_mixing_factor: Scalar,\n    variant_mixing_factor: Scalar,\n) -> ThemeProps {\n    new_default_theme_parameterized(\n        color_from_rgba(241, 250, 238, 1.0),\n        color_from_rgba(29, 53, 87, 1.0),\n        color_from_rgba(230, 57, 70, 1.0),\n        color_from_rgba(255, 255, 255, 1.0),\n        background_mixing_factor,\n        variant_mixing_factor,\n    )\n}\n\npub fn new_dark_theme() -> ThemeProps {\n    new_dark_theme_parameterized(\n        DEFAULT_BACKGROUND_MIXING_FACTOR,\n        DEFAULT_VARIANT_MIXING_FACTOR,\n    )\n}\n\npub fn new_dark_theme_parameterized(\n    background_mixing_factor: Scalar,\n    variant_mixing_factor: Scalar,\n) -> ThemeProps {\n    new_default_theme_parameterized(\n        color_from_rgba(64, 64, 64, 1.0),\n        color_from_rgba(255, 98, 86, 1.0),\n        color_from_rgba(0, 196, 228, 1.0),\n        color_from_rgba(32, 32, 32, 1.0),\n        background_mixing_factor,\n        variant_mixing_factor,\n    )\n}\n\npub fn new_all_white_theme() -> ThemeProps {\n    new_default_theme(\n        color_from_rgba(255, 255, 255, 1.0),\n        color_from_rgba(255, 255, 255, 1.0),\n        color_from_rgba(255, 255, 255, 1.0),\n        color_from_rgba(255, 255, 255, 1.0),\n    )\n}\n\npub fn new_default_theme(\n    default: Color,\n    primary: Color,\n    secondary: Color,\n    background: Color,\n) -> ThemeProps {\n    new_default_theme_parameterized(\n        default,\n        primary,\n        secondary,\n        background,\n        DEFAULT_BACKGROUND_MIXING_FACTOR,\n        DEFAULT_VARIANT_MIXING_FACTOR,\n    )\n}\n\npub fn new_default_theme_parameterized(\n    default: Color,\n    primary: Color,\n    secondary: Color,\n    background: Color,\n    background_mixing_factor: Scalar,\n    variant_mixing_factor: Scalar,\n) -> ThemeProps {\n    let background_primary = color_lerp(background, primary, background_mixing_factor);\n    let background_secondary = color_lerp(background, secondary, background_mixing_factor);\n    let mut background_modal = fluid_polarize_color(background);\n    background_modal.a = 0.75;\n    let mut content_backgrounds = HashMap::with_capacity(1);\n    content_backgrounds.insert(String::new(), Default::default());\n    let mut button_backgrounds = HashMap::with_capacity(1);\n    button_backgrounds.insert(String::new(), Default::default());\n    let mut text_variants = HashMap::with_capacity(1);\n    text_variants.insert(\n        String::new(),\n        ThemedTextMaterial {\n            font: TextBoxFont {\n                size: 18.0,\n                ..Default::default()\n            },\n            ..Default::default()\n        },\n    );\n    let mut switch_variants = HashMap::with_capacity(4);\n    switch_variants.insert(String::new(), ThemedSwitchMaterial::default());\n    switch_variants.insert(\"checkbox\".to_owned(), ThemedSwitchMaterial::default());\n    switch_variants.insert(\"toggle\".to_owned(), ThemedSwitchMaterial::default());\n    switch_variants.insert(\"radio\".to_owned(), ThemedSwitchMaterial::default());\n    let mut slider_variants = HashMap::with_capacity(1);\n    let mut modal_shadow_variants = HashMap::with_capacity(1);\n    slider_variants.insert(String::default(), ThemedSliderMaterial::default());\n    modal_shadow_variants.insert(String::new(), background_modal);\n    ThemeProps {\n        active_colors: make_colors_bundle(\n            make_color_set(default, variant_mixing_factor, variant_mixing_factor),\n            make_color_set(primary, variant_mixing_factor, variant_mixing_factor),\n            make_color_set(secondary, variant_mixing_factor, variant_mixing_factor),\n        ),\n        background_colors: make_colors_bundle(\n            make_color_set(background, variant_mixing_factor, variant_mixing_factor),\n            make_color_set(\n                background_primary,\n                variant_mixing_factor,\n                variant_mixing_factor,\n            ),\n            make_color_set(\n                background_secondary,\n                variant_mixing_factor,\n                variant_mixing_factor,\n            ),\n        ),\n        content_backgrounds,\n        button_backgrounds,\n        icons_level_sizes: vec![18.0, 24.0, 32.0, 48.0, 64.0, 128.0, 256.0, 512.0, 1024.0],\n        text_variants,\n        switch_variants,\n        slider_variants,\n        modal_shadow_variants,\n    }\n}\n\npub fn color_from_rgba(r: u8, g: u8, b: u8, a: Scalar) -> Color {\n    Color {\n        r: r as Scalar / 255.0,\n        g: g as Scalar / 255.0,\n        b: b as Scalar / 255.0,\n        a,\n    }\n}\n\npub fn make_colors_bundle(\n    default: ThemeColorSet,\n    primary: ThemeColorSet,\n    secondary: ThemeColorSet,\n) -> ThemeColorsBundle {\n    let contrast = ThemeColors {\n        default: ThemeColorSet {\n            main: contrast_color(default.main),\n            light: contrast_color(default.light),\n            dark: contrast_color(default.dark),\n        },\n        primary: ThemeColorSet {\n            main: contrast_color(primary.main),\n            light: contrast_color(primary.light),\n            dark: contrast_color(primary.dark),\n        },\n        secondary: ThemeColorSet {\n            main: contrast_color(secondary.main),\n            light: contrast_color(secondary.light),\n            dark: contrast_color(secondary.dark),\n        },\n    };\n    let main = ThemeColors {\n        default,\n        primary,\n        secondary,\n    };\n    ThemeColorsBundle { main, contrast }\n}\n\npub fn contrast_color(base_color: Color) -> Color {\n    Color {\n        r: 1.0 - base_color.r,\n        g: 1.0 - base_color.g,\n        b: 1.0 - base_color.b,\n        a: base_color.a,\n    }\n}\n\npub fn fluid_polarize(v: Scalar) -> Scalar {\n    (v - 0.5 * PI).sin() * 0.5 + 0.5\n}\n\npub fn fluid_polarize_color(color: Color) -> Color {\n    Color {\n        r: fluid_polarize(color.r),\n        g: fluid_polarize(color.g),\n        b: fluid_polarize(color.b),\n        a: color.a,\n    }\n}\n\npub fn make_color_set(base_color: Color, lighter: Scalar, darker: Scalar) -> ThemeColorSet {\n    let main = base_color;\n    let light = Color {\n        r: lerp_clamped(main.r, 1.0, lighter),\n        g: lerp_clamped(main.g, 1.0, lighter),\n        b: lerp_clamped(main.b, 1.0, lighter),\n        a: main.a,\n    };\n    let dark = Color {\n        r: lerp_clamped(main.r, 0.0, darker),\n        g: lerp_clamped(main.g, 0.0, darker),\n        b: lerp_clamped(main.b, 0.0, darker),\n        a: main.a,\n    };\n    ThemeColorSet { main, light, dark }\n}\n\npub fn color_lerp(from: Color, to: Color, factor: Scalar) -> Color {\n    Color {\n        r: lerp_clamped(from.r, to.r, factor),\n        g: lerp_clamped(from.g, to.g, factor),\n        b: lerp_clamped(from.b, to.b, factor),\n        a: lerp_clamped(from.a, to.a, factor),\n    }\n}\n"
  },
  {
    "path": "crates/retained/Cargo.toml",
    "content": "[package]\nname = \"raui-retained\"\nversion = \"0.70.17\"\nauthors = [\"Patryk 'PsichiX' Budzynski <psichix@gmail.com>\"]\nedition = \"2024\"\ndescription = \"RAUI retained mode UI layer\"\nreadme = \"../../README.md\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/RAUI-labs/raui\"\nkeywords = [\"renderer\", \"agnostic\", \"ui\", \"interface\", \"gamedev\"]\ncategories = [\"gui\", \"rendering::graphics-api\"]\n\n[dependencies]\nraui-core = { path = \"../core\", version = \"0.70\" }\n"
  },
  {
    "path": "crates/retained/src/lib.rs",
    "content": "use raui_core::{\n    Lifetime, LifetimeLazy, Managed, ValueReadAccess, ValueWriteAccess,\n    application::ChangeNotifier,\n    widget::{FnWidget, WidgetRef, component::WidgetComponent, context::*, node::WidgetNode},\n};\nuse std::{\n    any::Any,\n    marker::PhantomData,\n    ops::{Deref, DerefMut},\n    ptr::NonNull,\n};\n\n#[allow(unused_variables)]\npub trait ViewState: Any + Send + Sync {\n    fn on_mount(&mut self, context: WidgetMountOrChangeContext) {}\n    fn on_unmount(&mut self, context: WidgetUnmountContext) {}\n    fn on_change(&mut self, context: WidgetMountOrChangeContext) {}\n    fn on_render(&self, context: WidgetContext) -> WidgetNode;\n    fn as_any(&self) -> &dyn Any;\n    fn as_any_mut(&mut self) -> &mut dyn Any;\n}\n\npub struct View<T: ViewState> {\n    inner: Box<dyn ViewState>,\n    lifetime: Box<Lifetime>,\n    _phantom: PhantomData<fn() -> T>,\n}\n\nimpl<T: ViewState + Default> Default for View<T> {\n    fn default() -> Self {\n        Self::new(T::default())\n    }\n}\n\nimpl<T: ViewState> View<T> {\n    pub fn new(state: T) -> Self\n    where\n        T: 'static,\n    {\n        Self {\n            inner: Box::new(state),\n            lifetime: Default::default(),\n            _phantom: Default::default(),\n        }\n    }\n\n    pub fn into_inner(self) -> Box<dyn ViewState> {\n        self.inner\n    }\n\n    pub fn as_dyn(&'_ self) -> Option<ValueReadAccess<'_, dyn ViewState>> {\n        self.lifetime.read(&*self.inner)\n    }\n\n    pub fn as_dyn_mut(&'_ mut self) -> Option<ValueWriteAccess<'_, dyn ViewState>> {\n        self.lifetime.write(&mut *self.inner)\n    }\n\n    pub fn read(&'_ self) -> Option<ValueReadAccess<'_, T>> {\n        self.lifetime.read(self.inner.as_any().downcast_ref::<T>()?)\n    }\n\n    pub fn write(&'_ mut self) -> Option<ValueWriteAccess<'_, T>> {\n        self.lifetime\n            .write(self.inner.as_any_mut().downcast_mut::<T>()?)\n    }\n\n    pub fn lazy(&self) -> LazyView<T> {\n        unsafe {\n            let ptr = self.inner.as_any().downcast_ref::<T>().unwrap() as *const T as *mut T;\n            LazyView {\n                inner: NonNull::new_unchecked(ptr),\n                lifetime: self.lifetime.lazy(),\n            }\n        }\n    }\n\n    pub fn component(&self) -> WidgetComponent {\n        WidgetComponent::new(self.widget(), std::any::type_name::<Self>())\n    }\n\n    pub fn widget(&self) -> FnWidget {\n        let this = self.lazy();\n        FnWidget::closure(move |context| {\n            let this_mount = this.clone();\n            let this_unmount = this.clone();\n            let this_change = this.clone();\n            context.life_cycle.mount(move |context| {\n                if let Some(mut this) = this_mount.write() {\n                    this.on_mount(context);\n                }\n            });\n            context.life_cycle.unmount(move |context| {\n                if let Some(mut this) = this_unmount.write() {\n                    this.on_unmount(context);\n                }\n            });\n            context.life_cycle.change(move |context| {\n                if let Some(mut this) = this_change.write() {\n                    this.on_change(context);\n                }\n            });\n            this.write()\n                .map(|this| this.on_render(context))\n                .unwrap_or_default()\n        })\n    }\n}\n\npub struct LazyView<T: ViewState> {\n    inner: NonNull<T>,\n    lifetime: LifetimeLazy,\n}\n\nunsafe impl<T: ViewState> Send for LazyView<T> {}\nunsafe impl<T: ViewState> Sync for LazyView<T> {}\n\nimpl<T: ViewState> Clone for LazyView<T> {\n    fn clone(&self) -> Self {\n        Self {\n            inner: self.inner,\n            lifetime: self.lifetime.clone(),\n        }\n    }\n}\n\nimpl<T: ViewState> LazyView<T> {\n    pub fn as_dyn(&'_ self) -> Option<ValueReadAccess<'_, dyn ViewState>> {\n        unsafe { self.lifetime.read(self.inner.as_ref()) }\n    }\n\n    pub fn as_dyn_mut(&'_ self) -> Option<ValueWriteAccess<'_, dyn ViewState>> {\n        unsafe { self.lifetime.write(self.inner.as_ptr().as_mut()?) }\n    }\n\n    pub fn read(&'_ self) -> Option<ValueReadAccess<'_, T>> {\n        unsafe { self.lifetime.read(self.inner.as_ref()) }\n    }\n\n    pub fn write(&'_ self) -> Option<ValueWriteAccess<'_, T>> {\n        unsafe { self.lifetime.write(self.inner.as_ptr().as_mut()?) }\n    }\n}\n\npub struct ViewValue<T> {\n    inner: T,\n    notifier: Option<ChangeNotifier>,\n    id: WidgetRef,\n}\n\nimpl<T> ViewValue<T> {\n    pub fn new(value: T) -> Self {\n        Self {\n            inner: value,\n            notifier: None,\n            id: Default::default(),\n        }\n    }\n\n    pub fn with_notifier(mut self, notifier: ChangeNotifier) -> Self {\n        self.bind_notifier(notifier);\n        self\n    }\n\n    pub fn bind_notifier(&mut self, notifier: ChangeNotifier) {\n        if self.notifier.is_none()\n            && let Some(id) = self.id.read()\n        {\n            notifier.notify(id);\n        }\n        self.notifier = Some(notifier);\n    }\n\n    pub fn unbind_notifier(&mut self) {\n        self.notifier = None;\n    }\n\n    pub fn bound_notifier(&self) -> Option<&ChangeNotifier> {\n        self.notifier.as_ref()\n    }\n\n    pub fn widget_ref(&self) -> WidgetRef {\n        self.id.clone()\n    }\n}\n\nimpl<T> Deref for ViewValue<T> {\n    type Target = T;\n\n    fn deref(&self) -> &Self::Target {\n        &self.inner\n    }\n}\n\nimpl<T> DerefMut for ViewValue<T> {\n    fn deref_mut(&mut self) -> &mut Self::Target {\n        if let Some(notifier) = self.notifier.as_ref()\n            && let Some(id) = self.id.read()\n        {\n            notifier.notify(id);\n        }\n        &mut self.inner\n    }\n}\n\npub struct SharedView<T: ViewState> {\n    inner: Managed<Option<View<T>>>,\n}\n\nimpl<T: ViewState> Default for SharedView<T> {\n    fn default() -> Self {\n        Self {\n            inner: Default::default(),\n        }\n    }\n}\n\nimpl<T: ViewState> SharedView<T> {\n    pub fn new(view: View<T>) -> Self {\n        Self {\n            inner: Managed::new(Some(view)),\n        }\n    }\n\n    pub fn replace(&mut self, view: View<T>) {\n        if let Some(mut inner) = self.inner.write() {\n            *inner = Some(view);\n        }\n    }\n\n    pub fn clear(&mut self) {\n        if let Some(mut inner) = self.inner.write() {\n            *inner = None;\n        }\n    }\n\n    pub fn read(&'_ self) -> Option<ValueReadAccess<'_, View<T>>> {\n        self.inner.read()?.remap(|inner| inner.as_ref()).ok()\n    }\n\n    pub fn write(&'_ mut self) -> Option<ValueWriteAccess<'_, View<T>>> {\n        self.inner.write()?.remap(|inner| inner.as_mut()).ok()\n    }\n}\n\nimpl<T: ViewState> ViewState for SharedView<T> {\n    fn on_render(&self, context: WidgetContext) -> WidgetNode {\n        self.inner\n            .read()\n            .and_then(|inner| {\n                inner\n                    .as_ref()\n                    .map(|inner| inner.component().key(context.key).into())\n            })\n            .unwrap_or_default()\n    }\n\n    fn as_any(&self) -> &dyn Any {\n        self\n    }\n\n    fn as_any_mut(&mut self) -> &mut dyn Any {\n        self\n    }\n}\n"
  },
  {
    "path": "crates/tesselate-renderer/Cargo.toml",
    "content": "[package]\nname = \"raui-tesselate-renderer\"\nversion = \"0.70.17\"\nauthors = [\"Patryk 'PsichiX' Budzynski <psichix@gmail.com>\"]\nedition = \"2024\"\ndescription = \"RAUI renderer that tesselates layout into vertex and index buffers\"\nreadme = \"../../README.md\"\nlicense = \"MIT OR Apache-2.0\"\nrepository = \"https://github.com/RAUI-labs/raui\"\nkeywords = [\"renderer\", \"agnostic\", \"ui\", \"interface\", \"gamedev\"]\ncategories = [\"gui\", \"rendering::graphics-api\"]\n\n[features]\nindex32 = []\n\n[dependencies]\nraui-core = { path = \"../core\", version = \"0.70\" }\nserde = { version = \"1\", features = [\"derive\"] }\nvek = \"0.17\"\nspitfire-core = \"0.36\"\nspitfire-fontdue = \"0.36\"\nbytemuck = { version = \"1\", features = [\"derive\"] }\nfontdue = \"0.9\"\n"
  },
  {
    "path": "crates/tesselate-renderer/src/lib.rs",
    "content": "use bytemuck::Pod;\nuse fontdue::{\n    Font,\n    layout::{\n        CoordinateSystem, HorizontalAlign, Layout as TextLayout, LayoutSettings, TextStyle,\n        VerticalAlign,\n    },\n};\nuse raui_core::{\n    Scalar,\n    layout::{CoordsMapping, Layout},\n    renderer::Renderer,\n    widget::{\n        WidgetId,\n        unit::{\n            WidgetUnit,\n            image::{\n                ImageBoxColor, ImageBoxImage, ImageBoxImageScaling, ImageBoxMaterial,\n                ImageBoxProceduralMesh,\n            },\n            text::{TextBoxHorizontalAlign, TextBoxVerticalAlign},\n        },\n        utils::{Color, Rect, Transform, Vec2, lerp},\n    },\n};\nuse spitfire_core::{Triangle, VertexStream};\nuse spitfire_fontdue::{TextRenderer, TextVertex};\nuse std::collections::HashMap;\n\n#[derive(Debug, Clone)]\npub enum Error {\n    WidgetHasNoLayout(WidgetId),\n    UnsupportedImageMaterial(Box<ImageBoxMaterial>),\n    FontNotFound(String),\n    ImageNotFound(String),\n}\n\npub trait TesselateVertex: Pod {\n    fn apply(&mut self, position: [f32; 2], tex_coord: [f32; 3], color: [f32; 4]);\n    fn transform(&mut self, matrix: vek::Mat4<f32>);\n}\n\n#[derive(Debug, Clone, PartialEq)]\npub enum TesselateBatch {\n    Color,\n    Image {\n        id: String,\n    },\n    Text,\n    Procedural {\n        id: String,\n        images: Vec<String>,\n        parameters: HashMap<String, Scalar>,\n    },\n    ClipPush {\n        x: f32,\n        y: f32,\n        w: f32,\n        h: f32,\n    },\n    ClipPop,\n    Debug,\n}\n\npub trait TesselateResourceProvider {\n    fn image_id_and_uv_and_size_by_atlas_id(&self, id: &str) -> Option<(String, Rect, Vec2)>;\n    fn fonts(&self) -> &[Font];\n    fn font_index_by_id(&self, id: &str) -> Option<usize>;\n}\n\npub trait TesselateBatchConverter<B> {\n    fn convert(&mut self, batch: TesselateBatch) -> Option<B>;\n}\n\nimpl TesselateBatchConverter<TesselateBatch> for () {\n    fn convert(&mut self, batch: TesselateBatch) -> Option<TesselateBatch> {\n        Some(batch)\n    }\n}\n\n#[derive(Debug, Default, Clone, Copy)]\npub struct TessselateRendererDebug {\n    pub render_non_visual_nodes: bool,\n}\n\npub struct TesselateRenderer<'a, V, B, P, C>\nwhere\n    V: TesselateVertex + TextVertex<Color> + Default,\n    B: PartialEq,\n    P: TesselateResourceProvider,\n    C: TesselateBatchConverter<B>,\n{\n    provider: &'a P,\n    converter: &'a mut C,\n    stream: &'a mut VertexStream<V, B>,\n    text_renderer: &'a mut TextRenderer<Color>,\n    transform_stack: Vec<vek::Mat4<Scalar>>,\n    debug: Option<TessselateRendererDebug>,\n}\n\nimpl<'a, V, B, P, C> TesselateRenderer<'a, V, B, P, C>\nwhere\n    V: TesselateVertex + TextVertex<Color> + Default,\n    B: PartialEq,\n    C: TesselateBatchConverter<B>,\n    P: TesselateResourceProvider,\n{\n    pub fn new(\n        provider: &'a P,\n        converter: &'a mut C,\n        stream: &'a mut VertexStream<V, B>,\n        text_renderer: &'a mut TextRenderer<Color>,\n        debug: Option<TessselateRendererDebug>,\n    ) -> Self {\n        Self {\n            provider,\n            converter,\n            stream,\n            text_renderer,\n            transform_stack: Default::default(),\n            debug,\n        }\n    }\n\n    fn push_transform(&mut self, transform: &Transform, rect: Rect) {\n        let size = rect.size();\n        let offset = vek::Vec2::new(rect.left, rect.top);\n        let offset = vek::Mat4::<Scalar>::translation_2d(offset);\n        let pivot = vek::Vec2::new(\n            lerp(0.0, size.x, transform.pivot.x),\n            lerp(0.0, size.y, transform.pivot.y),\n        );\n        let pivot = vek::Mat4::<Scalar>::translation_2d(pivot);\n        let inv_pivot = pivot.inverted();\n        let align = vek::Vec2::new(\n            lerp(0.0, size.x, transform.align.x),\n            lerp(0.0, size.y, transform.align.y),\n        );\n        let align = vek::Mat4::<Scalar>::translation_2d(align);\n        let translate = vek::Mat4::<Scalar>::translation_2d(raui_to_vec2(transform.translation));\n        let rotate = vek::Mat4::<Scalar>::rotation_z(transform.rotation);\n        let scale = vek::Mat4::<Scalar>::scaling_3d(raui_to_vec2(transform.scale).with_z(1.0));\n        let skew = vek::Mat4::<Scalar>::from(vek::Mat2::new(\n            1.0,\n            transform.skew.y.tan(),\n            transform.skew.x.tan(),\n            1.0,\n        ));\n        let matrix = offset * align * pivot * translate * rotate * scale * skew * inv_pivot;\n        self.push_matrix(matrix);\n    }\n\n    fn push_transform_simple(&mut self, rect: Rect) {\n        let offset = vek::Vec2::new(rect.left, rect.top);\n        let offset = vek::Mat4::<Scalar>::translation_2d(offset);\n        self.push_matrix(offset);\n    }\n\n    fn push_matrix(&mut self, matrix: vek::Mat4<Scalar>) {\n        let matrix = self.top_transform() * matrix;\n        self.transform_stack.push(matrix);\n    }\n\n    fn pop_transform(&mut self) {\n        self.transform_stack.pop();\n    }\n\n    fn top_transform(&self) -> vek::Mat4<Scalar> {\n        self.transform_stack.last().cloned().unwrap_or_default()\n    }\n\n    fn make_vertex(position: Vec2, tex_coord: Vec2, page: Scalar, color: Color) -> V {\n        let mut result = V::default();\n        TesselateVertex::apply(\n            &mut result,\n            [position.x, position.y],\n            [tex_coord.x, tex_coord.y, page],\n            [color.r, color.g, color.b, color.a],\n        );\n        result\n    }\n\n    fn make_tiled_triangle_first(offset: usize) -> Triangle {\n        Triangle { a: 0, b: 1, c: 5 }.offset(offset)\n    }\n\n    fn make_tiled_triangle_second(offset: usize) -> Triangle {\n        Triangle { a: 5, b: 4, c: 0 }.offset(offset)\n    }\n\n    fn produce_color_triangles(&mut self, size: Vec2, scale: Vec2, data: &ImageBoxColor) {\n        let matrix = self.top_transform();\n        let tl = vec2_to_raui(matrix.mul_point(vek::Vec2::new(0.0, 0.0)));\n        let tr = vec2_to_raui(matrix.mul_point(vek::Vec2::new(size.x, 0.0)));\n        let br = vec2_to_raui(matrix.mul_point(vek::Vec2::new(size.x, size.y)));\n        let bl = vec2_to_raui(matrix.mul_point(vek::Vec2::new(0.0, size.y)));\n        let c = data.color;\n        match &data.scaling {\n            ImageBoxImageScaling::Stretch => {\n                if let Some(batch) = self.converter.convert(TesselateBatch::Color) {\n                    self.stream.batch_optimized(batch);\n                    self.stream.quad([\n                        Self::make_vertex(tl, Default::default(), 0.0, c),\n                        Self::make_vertex(tr, Default::default(), 0.0, c),\n                        Self::make_vertex(br, Default::default(), 0.0, c),\n                        Self::make_vertex(bl, Default::default(), 0.0, c),\n                    ]);\n                }\n            }\n            ImageBoxImageScaling::Frame(frame) => {\n                let mut d = frame.destination;\n                d.left *= scale.x;\n                d.right *= scale.x;\n                d.top *= scale.y;\n                d.bottom *= scale.y;\n                if d.left + d.right > size.x {\n                    let m = d.left + d.right;\n                    d.left = size.x * d.left / m;\n                    d.right = size.x * d.right / m;\n                }\n                if d.top + d.bottom > size.y {\n                    let m = d.top + d.bottom;\n                    d.top = size.y * d.top / m;\n                    d.bottom = size.y * d.bottom / m;\n                }\n                let til = vec2_to_raui(matrix.mul_point(vek::Vec2::new(d.left, 0.0)));\n                let tir = vec2_to_raui(matrix.mul_point(vek::Vec2::new(size.x - d.right, 0.0)));\n                let itr = vec2_to_raui(matrix.mul_point(vek::Vec2::new(size.x, d.top)));\n                let ibr = vec2_to_raui(matrix.mul_point(vek::Vec2::new(size.x, size.y - d.bottom)));\n                let bir = vec2_to_raui(matrix.mul_point(vek::Vec2::new(size.x - d.right, size.y)));\n                let bil = vec2_to_raui(matrix.mul_point(vek::Vec2::new(d.left, size.y)));\n                let ibl = vec2_to_raui(matrix.mul_point(vek::Vec2::new(0.0, size.y - d.bottom)));\n                let itl = vec2_to_raui(matrix.mul_point(vek::Vec2::new(0.0, d.top)));\n                let itil = vec2_to_raui(matrix.mul_point(vek::Vec2::new(d.left, d.top)));\n                let itir = vec2_to_raui(matrix.mul_point(vek::Vec2::new(size.x - d.right, d.top)));\n                let ibir = vec2_to_raui(\n                    matrix.mul_point(vek::Vec2::new(size.x - d.right, size.y - d.bottom)),\n                );\n                let ibil =\n                    vec2_to_raui(matrix.mul_point(vek::Vec2::new(d.left, size.y - d.bottom)));\n                if let Some(batch) = self.converter.convert(TesselateBatch::Color) {\n                    self.stream.batch_optimized(batch);\n                    self.stream.extend(\n                        [\n                            Self::make_vertex(tl, Default::default(), 0.0, c),\n                            Self::make_vertex(til, Default::default(), 0.0, c),\n                            Self::make_vertex(tir, Default::default(), 0.0, c),\n                            Self::make_vertex(tr, Default::default(), 0.0, c),\n                            Self::make_vertex(itl, Default::default(), 0.0, c),\n                            Self::make_vertex(itil, Default::default(), 0.0, c),\n                            Self::make_vertex(itir, Default::default(), 0.0, c),\n                            Self::make_vertex(itr, Default::default(), 0.0, c),\n                            Self::make_vertex(ibl, Default::default(), 0.0, c),\n                            Self::make_vertex(ibil, Default::default(), 0.0, c),\n                            Self::make_vertex(ibir, Default::default(), 0.0, c),\n                            Self::make_vertex(ibr, Default::default(), 0.0, c),\n                            Self::make_vertex(bl, Default::default(), 0.0, c),\n                            Self::make_vertex(bil, Default::default(), 0.0, c),\n                            Self::make_vertex(bir, Default::default(), 0.0, c),\n                            Self::make_vertex(br, Default::default(), 0.0, c),\n                        ],\n                        [\n                            Self::make_tiled_triangle_first(0),\n                            Self::make_tiled_triangle_second(0),\n                            Self::make_tiled_triangle_first(1),\n                            Self::make_tiled_triangle_second(1),\n                            Self::make_tiled_triangle_first(2),\n                            Self::make_tiled_triangle_second(2),\n                            Self::make_tiled_triangle_first(4),\n                            Self::make_tiled_triangle_second(4),\n                            Self::make_tiled_triangle_first(6),\n                            Self::make_tiled_triangle_second(6),\n                            Self::make_tiled_triangle_first(8),\n                            Self::make_tiled_triangle_second(8),\n                            Self::make_tiled_triangle_first(9),\n                            Self::make_tiled_triangle_second(9),\n                            Self::make_tiled_triangle_first(10),\n                            Self::make_tiled_triangle_second(10),\n                        ]\n                        .into_iter()\n                        .chain((!frame.frame_only).then(|| Self::make_tiled_triangle_first(5)))\n                        .chain((!frame.frame_only).then(|| Self::make_tiled_triangle_second(5))),\n                    );\n                }\n            }\n        }\n    }\n\n    fn produce_image_triangles(\n        &mut self,\n        id: String,\n        uvs: Rect,\n        size: Vec2,\n        rect: Rect,\n        scale: Vec2,\n        data: &ImageBoxImage,\n    ) {\n        let matrix = self.top_transform();\n        let tl = vec2_to_raui(matrix.mul_point(vek::Vec2::new(rect.left, rect.top)));\n        let tr = vec2_to_raui(matrix.mul_point(vek::Vec2::new(rect.right, rect.top)));\n        let br = vec2_to_raui(matrix.mul_point(vek::Vec2::new(rect.right, rect.bottom)));\n        let bl = vec2_to_raui(matrix.mul_point(vek::Vec2::new(rect.left, rect.bottom)));\n        let ctl = Vec2 {\n            x: uvs.left,\n            y: uvs.top,\n        };\n        let ctr = Vec2 {\n            x: uvs.right,\n            y: uvs.top,\n        };\n        let cbr = Vec2 {\n            x: uvs.right,\n            y: uvs.bottom,\n        };\n        let cbl = Vec2 {\n            x: uvs.left,\n            y: uvs.bottom,\n        };\n        let c = data.tint;\n        match &data.scaling {\n            ImageBoxImageScaling::Stretch => {\n                if let Some(batch) = self.converter.convert(TesselateBatch::Image { id }) {\n                    self.stream.batch_optimized(batch);\n                    self.stream.quad([\n                        Self::make_vertex(tl, ctl, 0.0, c),\n                        Self::make_vertex(tr, ctr, 0.0, c),\n                        Self::make_vertex(br, cbr, 0.0, c),\n                        Self::make_vertex(bl, cbl, 0.0, c),\n                    ]);\n                }\n            }\n            ImageBoxImageScaling::Frame(frame) => {\n                let inv_size = Vec2 {\n                    x: 1.0 / size.x,\n                    y: 1.0 / size.y,\n                };\n                let mut d = frame.destination;\n                d.left *= scale.x;\n                d.right *= scale.x;\n                d.top *= scale.y;\n                d.bottom *= scale.y;\n                if frame.frame_keep_aspect_ratio {\n                    d.left = (frame.source.left * rect.height()) / size.y;\n                    d.right = (frame.source.right * rect.height()) / size.y;\n                    d.top = (frame.source.top * rect.width()) / size.x;\n                    d.bottom = (frame.source.bottom * rect.width()) / size.x;\n                }\n                if d.left + d.right > rect.width() {\n                    let m = d.left + d.right;\n                    d.left = rect.width() * d.left / m;\n                    d.right = rect.width() * d.right / m;\n                }\n                if d.top + d.bottom > rect.height() {\n                    let m = d.top + d.bottom;\n                    d.top = rect.height() * d.top / m;\n                    d.bottom = rect.height() * d.bottom / m;\n                }\n                let til =\n                    vec2_to_raui(matrix.mul_point(vek::Vec2::new(rect.left + d.left, rect.top)));\n                let tir =\n                    vec2_to_raui(matrix.mul_point(vek::Vec2::new(rect.right - d.right, rect.top)));\n                let itr =\n                    vec2_to_raui(matrix.mul_point(vek::Vec2::new(rect.right, rect.top + d.top)));\n                let ibr = vec2_to_raui(\n                    matrix.mul_point(vek::Vec2::new(rect.right, rect.bottom - d.bottom)),\n                );\n                let bir = vec2_to_raui(\n                    matrix.mul_point(vek::Vec2::new(rect.right - d.right, rect.bottom)),\n                );\n                let bil =\n                    vec2_to_raui(matrix.mul_point(vek::Vec2::new(rect.left + d.left, rect.bottom)));\n                let ibl = vec2_to_raui(\n                    matrix.mul_point(vek::Vec2::new(rect.left, rect.bottom - d.bottom)),\n                );\n                let itl =\n                    vec2_to_raui(matrix.mul_point(vek::Vec2::new(rect.left, rect.top + d.top)));\n                let itil = vec2_to_raui(\n                    matrix.mul_point(vek::Vec2::new(rect.left + d.left, rect.top + d.top)),\n                );\n                let itir = vec2_to_raui(\n                    matrix.mul_point(vek::Vec2::new(rect.right - d.right, rect.top + d.top)),\n                );\n                let ibir = vec2_to_raui(\n                    matrix.mul_point(vek::Vec2::new(rect.right - d.right, rect.bottom - d.bottom)),\n                );\n                let ibil = vec2_to_raui(\n                    matrix.mul_point(vek::Vec2::new(rect.left + d.left, rect.bottom - d.bottom)),\n                );\n                let ctil = Vec2 {\n                    x: uvs.left + frame.source.left * inv_size.x,\n                    y: uvs.top,\n                };\n                let ctir = Vec2 {\n                    x: uvs.right - frame.source.right * inv_size.x,\n                    y: uvs.top,\n                };\n                let citr = Vec2 {\n                    x: uvs.right,\n                    y: uvs.top + frame.source.top * inv_size.y,\n                };\n                let cibr = Vec2 {\n                    x: uvs.right,\n                    y: uvs.bottom - frame.source.bottom * inv_size.y,\n                };\n                let cbir = Vec2 {\n                    x: uvs.right - frame.source.right * inv_size.x,\n                    y: uvs.bottom,\n                };\n                let cbil = Vec2 {\n                    x: uvs.left + frame.source.left * inv_size.x,\n                    y: uvs.bottom,\n                };\n                let cibl = Vec2 {\n                    x: uvs.left,\n                    y: uvs.bottom - frame.source.bottom * inv_size.y,\n                };\n                let citl = Vec2 {\n                    x: uvs.left,\n                    y: uvs.top + frame.source.top * inv_size.y,\n                };\n                let citil = Vec2 {\n                    x: uvs.left + frame.source.left * inv_size.x,\n                    y: uvs.top + frame.source.top * inv_size.y,\n                };\n                let citir = Vec2 {\n                    x: uvs.right - frame.source.right * inv_size.x,\n                    y: uvs.top + frame.source.top * inv_size.y,\n                };\n                let cibir = Vec2 {\n                    x: uvs.right - frame.source.right * inv_size.x,\n                    y: uvs.bottom - frame.source.bottom * inv_size.y,\n                };\n                let cibil = Vec2 {\n                    x: uvs.left + frame.source.left * inv_size.x,\n                    y: uvs.bottom - frame.source.bottom * inv_size.y,\n                };\n                if let Some(batch) = self.converter.convert(TesselateBatch::Image { id }) {\n                    self.stream.batch_optimized(batch);\n                    self.stream.extend(\n                        [\n                            Self::make_vertex(tl, ctl, 0.0, c),\n                            Self::make_vertex(til, ctil, 0.0, c),\n                            Self::make_vertex(tir, ctir, 0.0, c),\n                            Self::make_vertex(tr, ctr, 0.0, c),\n                            Self::make_vertex(itl, citl, 0.0, c),\n                            Self::make_vertex(itil, citil, 0.0, c),\n                            Self::make_vertex(itir, citir, 0.0, c),\n                            Self::make_vertex(itr, citr, 0.0, c),\n                            Self::make_vertex(ibl, cibl, 0.0, c),\n                            Self::make_vertex(ibil, cibil, 0.0, c),\n                            Self::make_vertex(ibir, cibir, 0.0, c),\n                            Self::make_vertex(ibr, cibr, 0.0, c),\n                            Self::make_vertex(bl, cbl, 0.0, c),\n                            Self::make_vertex(bil, cbil, 0.0, c),\n                            Self::make_vertex(bir, cbir, 0.0, c),\n                            Self::make_vertex(br, cbr, 0.0, c),\n                        ],\n                        [\n                            Self::make_tiled_triangle_first(0),\n                            Self::make_tiled_triangle_second(0),\n                            Self::make_tiled_triangle_first(1),\n                            Self::make_tiled_triangle_second(1),\n                            Self::make_tiled_triangle_first(2),\n                            Self::make_tiled_triangle_second(2),\n                            Self::make_tiled_triangle_first(4),\n                            Self::make_tiled_triangle_second(4),\n                            Self::make_tiled_triangle_first(6),\n                            Self::make_tiled_triangle_second(6),\n                            Self::make_tiled_triangle_first(8),\n                            Self::make_tiled_triangle_second(8),\n                            Self::make_tiled_triangle_first(9),\n                            Self::make_tiled_triangle_second(9),\n                            Self::make_tiled_triangle_first(10),\n                            Self::make_tiled_triangle_second(10),\n                        ]\n                        .into_iter()\n                        .chain((!frame.frame_only).then(|| Self::make_tiled_triangle_first(5)))\n                        .chain((!frame.frame_only).then(|| Self::make_tiled_triangle_second(5))),\n                    );\n                }\n            }\n        }\n    }\n\n    fn produce_debug_wireframe(&mut self, size: Vec2) {\n        if let Some(batch) = self.converter.convert(TesselateBatch::Debug) {\n            let rect = Rect {\n                left: 0.0,\n                right: size.x,\n                top: 0.0,\n                bottom: size.y,\n            };\n            let matrix = self.top_transform();\n            let tl = vec2_to_raui(matrix.mul_point(vek::Vec2::new(rect.left, rect.top)));\n            let tr = vec2_to_raui(matrix.mul_point(vek::Vec2::new(rect.right, rect.top)));\n            let br = vec2_to_raui(matrix.mul_point(vek::Vec2::new(rect.right, rect.bottom)));\n            let bl = vec2_to_raui(matrix.mul_point(vek::Vec2::new(rect.left, rect.bottom)));\n            self.stream.batch_optimized(batch);\n            self.stream.quad([\n                Self::make_vertex(tl, Default::default(), 0.0, Default::default()),\n                Self::make_vertex(tr, Default::default(), 0.0, Default::default()),\n                Self::make_vertex(br, Default::default(), 0.0, Default::default()),\n                Self::make_vertex(bl, Default::default(), 0.0, Default::default()),\n            ]);\n        }\n    }\n\n    fn render_node(\n        &mut self,\n        unit: &WidgetUnit,\n        mapping: &CoordsMapping,\n        layout: &Layout,\n        local: bool,\n    ) -> Result<(), Error> {\n        match unit {\n            WidgetUnit::None | WidgetUnit::PortalBox(_) => Ok(()),\n            WidgetUnit::AreaBox(unit) => {\n                if let Some(item) = layout.items.get(&unit.id) {\n                    let local_space = mapping.virtual_to_real_rect(item.local_space, local);\n                    self.push_transform_simple(local_space);\n                    self.render_node(&unit.slot, mapping, layout, true)?;\n                    self.pop_transform();\n                    Ok(())\n                } else {\n                    Err(Error::WidgetHasNoLayout(unit.id.to_owned()))\n                }\n            }\n            WidgetUnit::ContentBox(unit) => {\n                if let Some(item) = layout.items.get(&unit.id) {\n                    let mut items = unit\n                        .items\n                        .iter()\n                        .map(|item| (item.layout.depth, item))\n                        .collect::<Vec<_>>();\n                    items.sort_unstable_by(|(a, _), (b, _)| a.partial_cmp(b).unwrap());\n                    let local_space = mapping.virtual_to_real_rect(item.local_space, local);\n                    self.push_transform(&unit.transform, local_space);\n                    if unit.clipping {\n                        let size = local_space.size();\n                        let matrix = self.top_transform();\n                        let tl = matrix.mul_point(vek::Vec2::new(0.0, 0.0));\n                        let tr = matrix.mul_point(vek::Vec2::new(size.x, 0.0));\n                        let br = matrix.mul_point(vek::Vec2::new(size.x, size.y));\n                        let bl = matrix.mul_point(vek::Vec2::new(0.0, size.y));\n                        let x = tl.x.min(tr.x).min(br.x).min(bl.x).round();\n                        let y = tl.y.min(tr.y).min(br.y).min(bl.y).round();\n                        let x2 = tl.x.max(tr.x).max(br.x).max(bl.x).round();\n                        let y2 = tl.y.max(tr.y).max(br.y).max(bl.y).round();\n                        let w = x2 - x;\n                        let h = y2 - y;\n                        if let Some(batch) =\n                            self.converter\n                                .convert(TesselateBatch::ClipPush { x, y, w, h })\n                        {\n                            self.stream.batch(batch);\n                            self.stream.batch_end();\n                        }\n                    }\n                    for (_, item) in items {\n                        self.render_node(&item.slot, mapping, layout, true)?;\n                    }\n                    if unit.clipping\n                        && let Some(batch) = self.converter.convert(TesselateBatch::ClipPop)\n                    {\n                        self.stream.batch(batch);\n                        self.stream.batch_end();\n                    }\n                    self.pop_transform();\n                    Ok(())\n                } else {\n                    Err(Error::WidgetHasNoLayout(unit.id.to_owned()))\n                }\n            }\n            WidgetUnit::FlexBox(unit) => {\n                if let Some(item) = layout.items.get(&unit.id) {\n                    let local_space = mapping.virtual_to_real_rect(item.local_space, local);\n                    self.push_transform(&unit.transform, local_space);\n                    for item in &unit.items {\n                        self.render_node(&item.slot, mapping, layout, true)?;\n                    }\n                    self.pop_transform();\n                    Ok(())\n                } else {\n                    Err(Error::WidgetHasNoLayout(unit.id.to_owned()))\n                }\n            }\n            WidgetUnit::GridBox(unit) => {\n                if let Some(item) = layout.items.get(&unit.id) {\n                    let local_space = mapping.virtual_to_real_rect(item.local_space, local);\n                    self.push_transform(&unit.transform, local_space);\n                    for item in &unit.items {\n                        self.render_node(&item.slot, mapping, layout, true)?;\n                    }\n                    self.pop_transform();\n                    Ok(())\n                } else {\n                    Err(Error::WidgetHasNoLayout(unit.id.to_owned()))\n                }\n            }\n            WidgetUnit::SizeBox(unit) => {\n                if let Some(item) = layout.items.get(&unit.id) {\n                    let local_space = mapping.virtual_to_real_rect(item.local_space, local);\n                    self.push_transform(&unit.transform, local_space);\n                    self.render_node(&unit.slot, mapping, layout, true)?;\n                    self.pop_transform();\n                    Ok(())\n                } else {\n                    Err(Error::WidgetHasNoLayout(unit.id.to_owned()))\n                }\n            }\n            WidgetUnit::ImageBox(unit) => match &unit.material {\n                ImageBoxMaterial::Color(color) => {\n                    if let Some(item) = layout.items.get(&unit.id) {\n                        let local_space = mapping.virtual_to_real_rect(item.local_space, local);\n                        self.push_transform(&unit.transform, local_space);\n                        self.produce_color_triangles(local_space.size(), mapping.scale(), color);\n                        self.pop_transform();\n                        Ok(())\n                    } else {\n                        Err(Error::WidgetHasNoLayout(unit.id.to_owned()))\n                    }\n                }\n                ImageBoxMaterial::Image(image) => {\n                    if let Some(item) = layout.items.get(&unit.id) {\n                        let local_space = mapping.virtual_to_real_rect(item.local_space, local);\n                        let rect = Rect {\n                            left: 0.0,\n                            right: local_space.width(),\n                            top: 0.0,\n                            bottom: local_space.height(),\n                        };\n                        let (id, uvs, size) = match self\n                            .provider\n                            .image_id_and_uv_and_size_by_atlas_id(&image.id)\n                        {\n                            Some(result) => result,\n                            None => return Err(Error::ImageNotFound(image.id.to_owned())),\n                        };\n                        let rect = if let Some(aspect) = unit.content_keep_aspect_ratio {\n                            let ox = rect.left;\n                            let oy = rect.top;\n                            let iw = rect.width();\n                            let ih = rect.height();\n                            let ra = size.x / size.y;\n                            let ia = iw / ih;\n                            let scale = if (ra >= ia) != aspect.outside {\n                                iw / size.x\n                            } else {\n                                ih / size.y\n                            };\n                            let w = size.x * scale;\n                            let h = size.y * scale;\n                            let ow = lerp(0.0, iw - w, aspect.horizontal_alignment);\n                            let oh = lerp(0.0, ih - h, aspect.vertical_alignment);\n                            Rect {\n                                left: ox + ow,\n                                right: ox + ow + w,\n                                top: oy + oh,\n                                bottom: oy + oh + h,\n                            }\n                        } else {\n                            rect\n                        };\n                        self.push_transform(&unit.transform, local_space);\n                        self.produce_image_triangles(id, uvs, size, rect, mapping.scale(), image);\n                        self.pop_transform();\n                        Ok(())\n                    } else {\n                        Err(Error::WidgetHasNoLayout(unit.id.to_owned()))\n                    }\n                }\n                ImageBoxMaterial::Procedural(procedural) => {\n                    if let Some(item) = layout.items.get(&unit.id) {\n                        let local_space = mapping.virtual_to_real_rect(item.local_space, local);\n                        self.push_transform(&unit.transform, local_space);\n                        if let Some(batch) = self.converter.convert(TesselateBatch::Procedural {\n                            id: procedural.id.to_owned(),\n                            images: procedural.images.to_owned(),\n                            parameters: procedural.parameters.to_owned(),\n                        }) {\n                            let image_mapping =\n                                CoordsMapping::new_scaling(local_space, procedural.vertex_mapping);\n                            self.stream.batch_optimized(batch);\n                            match &procedural.mesh {\n                                ImageBoxProceduralMesh::Owned(mesh) => {\n                                    self.stream.extend(\n                                        mesh.vertices.iter().map(|vertex| {\n                                            Self::make_vertex(\n                                                image_mapping\n                                                    .virtual_to_real_vec2(vertex.position, false),\n                                                vertex.tex_coord,\n                                                vertex.page,\n                                                vertex.color,\n                                            )\n                                        }),\n                                        mesh.triangles.iter().map(|triangle| Triangle {\n                                            a: triangle[0],\n                                            b: triangle[1],\n                                            c: triangle[2],\n                                        }),\n                                    );\n                                }\n                                ImageBoxProceduralMesh::Shared(mesh) => {\n                                    self.stream.extend(\n                                        mesh.vertices.iter().map(|vertex| {\n                                            Self::make_vertex(\n                                                image_mapping\n                                                    .virtual_to_real_vec2(vertex.position, false),\n                                                vertex.tex_coord,\n                                                vertex.page,\n                                                vertex.color,\n                                            )\n                                        }),\n                                        mesh.triangles.iter().map(|triangle| Triangle {\n                                            a: triangle[0],\n                                            b: triangle[1],\n                                            c: triangle[2],\n                                        }),\n                                    );\n                                }\n                                ImageBoxProceduralMesh::Generator(generator) => {\n                                    let mesh = (generator)(local_space, &procedural.parameters);\n                                    self.stream.extend(\n                                        mesh.vertices.into_iter().map(|vertex| {\n                                            Self::make_vertex(\n                                                image_mapping\n                                                    .virtual_to_real_vec2(vertex.position, false),\n                                                vertex.tex_coord,\n                                                vertex.page,\n                                                vertex.color,\n                                            )\n                                        }),\n                                        mesh.triangles.into_iter().map(|triangle| Triangle {\n                                            a: triangle[0],\n                                            b: triangle[1],\n                                            c: triangle[2],\n                                        }),\n                                    );\n                                }\n                            }\n                        }\n                        self.pop_transform();\n                        Ok(())\n                    } else {\n                        Err(Error::WidgetHasNoLayout(unit.id.to_owned()))\n                    }\n                }\n            },\n            WidgetUnit::TextBox(unit) => {\n                let font_index = match self.provider.font_index_by_id(&unit.font.name) {\n                    Some(index) => index,\n                    None => return Err(Error::FontNotFound(unit.font.name.to_owned())),\n                };\n                if let Some(item) = layout.items.get(&unit.id) {\n                    let local_space = mapping.virtual_to_real_rect(item.local_space, local);\n                    let size = local_space.size();\n                    self.push_transform(&unit.transform, local_space);\n                    let matrix = self.top_transform();\n                    if let Some(batch) = self.converter.convert(TesselateBatch::Text) {\n                        self.stream.batch_optimized(batch);\n                        self.stream.transformed(\n                            |stream| {\n                                let text = TextStyle::with_user_data(\n                                    &unit.text,\n                                    unit.font.size * mapping.scalar_scale(false),\n                                    font_index,\n                                    unit.color,\n                                );\n                                let mut layout = TextLayout::new(CoordinateSystem::PositiveYDown);\n                                layout.reset(&LayoutSettings {\n                                    max_width: Some(size.x),\n                                    max_height: Some(size.y),\n                                    horizontal_align: match unit.horizontal_align {\n                                        TextBoxHorizontalAlign::Left => HorizontalAlign::Left,\n                                        TextBoxHorizontalAlign::Center => HorizontalAlign::Center,\n                                        TextBoxHorizontalAlign::Right => HorizontalAlign::Right,\n                                    },\n                                    vertical_align: match unit.vertical_align {\n                                        TextBoxVerticalAlign::Top => VerticalAlign::Top,\n                                        TextBoxVerticalAlign::Middle => VerticalAlign::Middle,\n                                        TextBoxVerticalAlign::Bottom => VerticalAlign::Bottom,\n                                    },\n                                    ..Default::default()\n                                });\n                                layout.append(self.provider.fonts(), &text);\n                                self.text_renderer.include(self.provider.fonts(), &layout);\n                                self.text_renderer.render_to_stream(stream);\n                            },\n                            |vertex| {\n                                vertex.transform(matrix);\n                            },\n                        );\n                    }\n                    self.pop_transform();\n                    Ok(())\n                } else {\n                    Err(Error::WidgetHasNoLayout(unit.id.to_owned()))\n                }\n            }\n        }\n    }\n\n    fn debug_render_node(\n        &mut self,\n        unit: &WidgetUnit,\n        mapping: &CoordsMapping,\n        layout: &Layout,\n        local: bool,\n        debug: TessselateRendererDebug,\n    ) {\n        match unit {\n            WidgetUnit::AreaBox(unit) => {\n                if let Some(item) = layout.items.get(&unit.id) {\n                    let local_space = mapping.virtual_to_real_rect(item.local_space, local);\n                    self.push_transform_simple(local_space);\n                    self.debug_render_node(&unit.slot, mapping, layout, true, debug);\n                    if debug.render_non_visual_nodes {\n                        self.produce_debug_wireframe(local_space.size());\n                    }\n                    self.pop_transform();\n                }\n            }\n            WidgetUnit::ContentBox(unit) => {\n                if let Some(item) = layout.items.get(&unit.id) {\n                    let mut items = unit\n                        .items\n                        .iter()\n                        .map(|item| (item.layout.depth, item))\n                        .collect::<Vec<_>>();\n                    items.sort_unstable_by(|(a, _), (b, _)| a.partial_cmp(b).unwrap());\n                    let local_space = mapping.virtual_to_real_rect(item.local_space, local);\n                    self.push_transform(&unit.transform, local_space);\n                    for (_, item) in items {\n                        self.debug_render_node(&item.slot, mapping, layout, true, debug);\n                    }\n                    if debug.render_non_visual_nodes {\n                        self.produce_debug_wireframe(local_space.size());\n                    }\n                    self.pop_transform();\n                }\n            }\n            WidgetUnit::FlexBox(unit) => {\n                if let Some(item) = layout.items.get(&unit.id) {\n                    let local_space = mapping.virtual_to_real_rect(item.local_space, local);\n                    self.push_transform(&unit.transform, local_space);\n                    for item in &unit.items {\n                        self.debug_render_node(&item.slot, mapping, layout, true, debug);\n                    }\n                    if debug.render_non_visual_nodes {\n                        self.produce_debug_wireframe(local_space.size());\n                    }\n                    self.pop_transform();\n                }\n            }\n            WidgetUnit::GridBox(unit) => {\n                if let Some(item) = layout.items.get(&unit.id) {\n                    let local_space = mapping.virtual_to_real_rect(item.local_space, local);\n                    self.push_transform(&unit.transform, local_space);\n                    for item in &unit.items {\n                        self.debug_render_node(&item.slot, mapping, layout, true, debug);\n                    }\n                    if debug.render_non_visual_nodes {\n                        self.produce_debug_wireframe(local_space.size());\n                    }\n                    self.pop_transform();\n                }\n            }\n            WidgetUnit::SizeBox(unit) => {\n                if let Some(item) = layout.items.get(&unit.id) {\n                    let local_space = mapping.virtual_to_real_rect(item.local_space, local);\n                    self.push_transform(&unit.transform, local_space);\n                    self.debug_render_node(&unit.slot, mapping, layout, true, debug);\n                    if debug.render_non_visual_nodes {\n                        self.produce_debug_wireframe(local_space.size());\n                    }\n                    self.pop_transform();\n                }\n            }\n            WidgetUnit::ImageBox(unit) => {\n                if let Some(item) = layout.items.get(&unit.id) {\n                    let local_space = mapping.virtual_to_real_rect(item.local_space, local);\n                    self.push_transform(&unit.transform, local_space);\n                    self.produce_debug_wireframe(local_space.size());\n                    self.pop_transform();\n                }\n            }\n            WidgetUnit::TextBox(unit) => {\n                if let Some(item) = layout.items.get(&unit.id) {\n                    let local_space = mapping.virtual_to_real_rect(item.local_space, local);\n                    self.push_transform(&unit.transform, local_space);\n                    self.produce_debug_wireframe(local_space.size());\n                    self.pop_transform();\n                }\n            }\n            _ => {}\n        }\n    }\n}\n\nimpl<V, B, P, C> Renderer<(), Error> for TesselateRenderer<'_, V, B, P, C>\nwhere\n    V: TesselateVertex + TextVertex<Color> + Default,\n    B: PartialEq,\n    C: TesselateBatchConverter<B>,\n    P: TesselateResourceProvider,\n{\n    fn render(\n        &mut self,\n        tree: &WidgetUnit,\n        mapping: &CoordsMapping,\n        layout: &Layout,\n    ) -> Result<(), Error> {\n        self.transform_stack.clear();\n        self.render_node(tree, mapping, layout, false)?;\n        self.stream.batch_end();\n        if let Some(debug) = self.debug {\n            self.transform_stack.clear();\n            self.debug_render_node(tree, mapping, layout, false, debug);\n            self.stream.batch_end();\n        }\n        Ok(())\n    }\n}\n\nfn raui_to_vec2(v: Vec2) -> vek::Vec2<Scalar> {\n    vek::Vec2::new(v.x, v.y)\n}\n\nfn vec2_to_raui(v: vek::Vec2<Scalar>) -> Vec2 {\n    Vec2 { x: v.x, y: v.y }\n}\n"
  },
  {
    "path": "demos/hello-world/Cargo.toml",
    "content": "[package]\nname = \"hello-world\"\nversion = \"0.1.0\"\nauthors = [\"Patryk 'PsichiX' Budzynski <psichix@gmail.com>\"]\nedition = \"2024\"\npublish = false\n\n[dependencies]\nserde = { version = \"1\", features = [\"derive\"] }\nraui = { version = \"0.70\", path = \"../../crates/_\", features = [\"app\"] }\n"
  },
  {
    "path": "demos/hello-world/src/main.rs",
    "content": "mod ui;\n\nuse crate::ui::{\n    components::{app::app, content::content, title_bar::title_bar},\n    view_models::AppData,\n};\nuse raui::{\n    app::app::{App, AppConfig, declarative::DeclarativeApp},\n    core::{make_widget, view_model::ViewModel},\n};\n\nfn main() {\n    let app = DeclarativeApp::default()\n        .view_model(AppData::VIEW_MODEL, ViewModel::produce(AppData::new))\n        .tree(\n            make_widget!(app)\n                .named_slot(\"title\", make_widget!(title_bar))\n                .named_slot(\"content\", make_widget!(content)),\n        );\n\n    App::new(AppConfig::default().title(\"Hello World!\")).run(app);\n}\n"
  },
  {
    "path": "demos/hello-world/src/ui/components/app.rs",
    "content": "use raui::core::{\n    make_widget, unpack_named_slots,\n    widget::{\n        component::{\n            containers::vertical_box::{VerticalBoxProps, nav_vertical_box},\n            interactive::navigation::NavJumpLooped,\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::flex::FlexBoxItemLayout,\n    },\n};\n\npub fn app(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        key, named_slots, ..\n    } = context;\n    unpack_named_slots!(named_slots => { title, content });\n\n    title.remap_props(|props| {\n        props.with(FlexBoxItemLayout {\n            grow: 0.0,\n            shrink: 0.0,\n            ..Default::default()\n        })\n    });\n\n    make_widget!(nav_vertical_box)\n        .key(key)\n        .with_props(VerticalBoxProps {\n            separation: 16.0,\n            ..Default::default()\n        })\n        .with_props(NavJumpLooped)\n        .listed_slot(title)\n        .listed_slot(content)\n        .into()\n}\n"
  },
  {
    "path": "demos/hello-world/src/ui/components/color_rect.rs",
    "content": "use raui::core::{\n    Prefab, PropsData, make_widget,\n    props::PropsData,\n    widget::{\n        component::image_box::{ImageBoxProps, image_box},\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::image::{ImageBoxColor, ImageBoxImageScaling, ImageBoxMaterial},\n        utils::Color,\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Default, Copy, Clone, Serialize, Deserialize)]\npub struct ColorRectProps {\n    #[serde(default)]\n    pub color: Color,\n}\n\npub fn color_rect(context: WidgetContext) -> WidgetNode {\n    let WidgetContext { key, props, .. } = context;\n\n    let color = props.read_cloned_or_default::<ColorRectProps>().color;\n\n    make_widget!(image_box)\n        .key(key)\n        .merge_props(props.clone())\n        .with_props(ImageBoxProps {\n            material: ImageBoxMaterial::Color(ImageBoxColor {\n                color,\n                scaling: ImageBoxImageScaling::Frame((10.0, true).into()),\n            }),\n            ..Default::default()\n        })\n        .into()\n}\n"
  },
  {
    "path": "demos/hello-world/src/ui/components/content.rs",
    "content": "use crate::ui::components::{\n    color_rect::{ColorRectProps, color_rect},\n    image_button::{ImageButtonProps, image_button},\n};\nuse raui::core::{\n    make_widget,\n    widget::{\n        component::containers::grid_box::{GridBoxProps, grid_box},\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::grid::GridBoxItemLayout,\n        utils::{Color, IntRect, Rect},\n    },\n};\n\npub fn content(context: WidgetContext) -> WidgetNode {\n    let WidgetContext { key, props, .. } = context;\n\n    make_widget!(grid_box)\n        .key(key)\n        .merge_props(props.clone())\n        .with_props(GridBoxProps {\n            cols: 2,\n            rows: 2,\n            ..Default::default()\n        })\n        .listed_slot(\n            make_widget!(image_button)\n                .with_props(ImageButtonProps {\n                    image: \"./resources/cat.jpg\".to_owned(),\n                    horizontal_alignment: 1.0,\n                })\n                .with_props(GridBoxItemLayout {\n                    space_occupancy: IntRect {\n                        left: 0,\n                        right: 1,\n                        top: 0,\n                        bottom: 1,\n                    },\n                    margin: Rect {\n                        left: 8.0,\n                        right: 8.0,\n                        top: 8.0,\n                        bottom: 8.0,\n                    },\n                    ..Default::default()\n                }),\n        )\n        .listed_slot(\n            make_widget!(color_rect)\n                .with_props(ColorRectProps {\n                    color: Color {\n                        r: 1.0,\n                        g: 0.0,\n                        b: 0.0,\n                        a: 0.5,\n                    },\n                })\n                .with_props(GridBoxItemLayout {\n                    space_occupancy: IntRect {\n                        left: 1,\n                        right: 2,\n                        top: 0,\n                        bottom: 1,\n                    },\n                    margin: 8.0.into(),\n                    ..Default::default()\n                }),\n        )\n        .listed_slot(\n            make_widget!(image_button)\n                .with_props(ImageButtonProps {\n                    image: \"./resources/cats.jpg\".to_owned(),\n                    horizontal_alignment: 0.5,\n                })\n                .with_props(GridBoxItemLayout {\n                    space_occupancy: IntRect {\n                        left: 0,\n                        right: 2,\n                        top: 1,\n                        bottom: 2,\n                    },\n                    margin: 8.0.into(),\n                    ..Default::default()\n                }),\n        )\n        .into()\n}\n"
  },
  {
    "path": "demos/hello-world/src/ui/components/image_button.rs",
    "content": "use raui::core::{\n    Prefab, PropsData, Scalar, make_widget, pre_hooks,\n    props::PropsData,\n    widget::{\n        component::{\n            image_box::{ImageBoxProps, image_box},\n            interactive::{\n                button::{ButtonNotifyProps, ButtonProps, button, use_button_notified_state},\n                navigation::NavItemActive,\n            },\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::image::{ImageBoxAspectRatio, ImageBoxImage, ImageBoxMaterial},\n        utils::{Color, Transform, Vec2},\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\npub struct ImageButtonProps {\n    #[serde(default)]\n    pub image: String,\n    #[serde(default)]\n    pub horizontal_alignment: Scalar,\n}\n\n#[pre_hooks(use_button_notified_state)]\npub fn image_button(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        id,\n        key,\n        props,\n        state,\n        ..\n    } = context;\n\n    let ImageButtonProps {\n        image,\n        horizontal_alignment,\n    } = props.read_cloned_or_default();\n    let ButtonProps {\n        selected,\n        trigger,\n        context,\n        ..\n    } = state.read_cloned_or_default();\n    let scale = if trigger || context {\n        Vec2 { x: 1.1, y: 1.1 }\n    } else if selected {\n        Vec2 { x: 1.05, y: 1.05 }\n    } else {\n        Vec2 { x: 1.0, y: 1.0 }\n    };\n\n    make_widget!(button)\n        .key(key)\n        .with_props(NavItemActive)\n        .with_props(ButtonNotifyProps(id.to_owned().into()))\n        .named_slot(\n            \"content\",\n            make_widget!(image_box)\n                .key(\"image\")\n                .with_props(ImageBoxProps {\n                    material: ImageBoxMaterial::Image(ImageBoxImage {\n                        id: image,\n                        tint: if trigger {\n                            Color {\n                                r: 0.0,\n                                g: 1.0,\n                                b: 0.0,\n                                a: 1.0,\n                            }\n                        } else if context {\n                            Color {\n                                r: 1.0,\n                                g: 0.0,\n                                b: 0.0,\n                                a: 1.0,\n                            }\n                        } else if selected {\n                            Color {\n                                r: 1.0,\n                                g: 1.0,\n                                b: 1.0,\n                                a: 0.85,\n                            }\n                        } else {\n                            Color::default()\n                        },\n                        ..Default::default()\n                    }),\n                    content_keep_aspect_ratio: Some(ImageBoxAspectRatio {\n                        horizontal_alignment,\n                        vertical_alignment: 0.5,\n                        outside: false,\n                    }),\n                    transform: Transform {\n                        pivot: Vec2 { x: 0.5, y: 0.5 },\n                        scale,\n                        ..Default::default()\n                    },\n                    ..Default::default()\n                }),\n        )\n        .into()\n}\n"
  },
  {
    "path": "demos/hello-world/src/ui/components/mod.rs",
    "content": "pub mod app;\npub mod color_rect;\npub mod content;\npub mod image_button;\npub mod title_bar;\n"
  },
  {
    "path": "demos/hello-world/src/ui/components/title_bar.rs",
    "content": "use crate::ui::view_models::AppData;\nuse raui::core::{\n    make_widget, pre_hooks,\n    widget::{\n        component::{\n            interactive::{\n                button::{ButtonNotifyProps, ButtonProps, use_button_notified_state},\n                input_field::{\n                    TextInputNotifyProps, TextInputProps, TextInputState, input_field,\n                    input_text_with_cursor, use_text_input_notified_state,\n                },\n                navigation::NavItemActive,\n            },\n            text_box::{TextBoxProps, text_box},\n        },\n        context::WidgetContext,\n        node::WidgetNode,\n        unit::text::{TextBoxFont, TextBoxSizeValue},\n        utils::Color,\n    },\n};\n\nfn use_title_bar(context: &mut WidgetContext) {\n    context.life_cycle.mount(|mut context| {\n        context\n            .view_models\n            .bindings(AppData::VIEW_MODEL, AppData::INPUT)\n            .unwrap()\n            .bind(context.id.to_owned());\n    });\n}\n\n#[pre_hooks(\n    use_button_notified_state,\n    use_text_input_notified_state,\n    use_title_bar\n)]\npub fn title_bar(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext { id, key, state, .. } = context;\n\n    let ButtonProps {\n        selected, trigger, ..\n    } = state.read_cloned_or_default();\n    let TextInputState {\n        cursor_position,\n        focused,\n    } = state.read_cloned_or_default();\n    let content = context\n        .view_models\n        .view_model_mut(AppData::VIEW_MODEL)\n        .unwrap()\n        .write::<AppData>()\n        .unwrap()\n        .input\n        .lazy();\n    let text = content\n        .read()\n        .map(|text| text.to_string())\n        .unwrap_or_default();\n    let text = if focused {\n        input_text_with_cursor(&text, cursor_position, '|')\n    } else if text.is_empty() {\n        \"> Focus here and start typing...\".to_owned()\n    } else {\n        text\n    };\n\n    make_widget!(input_field)\n        .key(key)\n        .with_props(NavItemActive)\n        .with_props(TextInputNotifyProps(id.to_owned().into()))\n        .with_props(ButtonNotifyProps(id.to_owned().into()))\n        .with_props(TextInputProps {\n            text: Some(content.into()),\n            ..Default::default()\n        })\n        .named_slot(\n            \"content\",\n            make_widget!(text_box).key(\"text\").with_props(TextBoxProps {\n                text,\n                width: TextBoxSizeValue::Fill,\n                height: TextBoxSizeValue::Exact(32.0),\n                font: TextBoxFont {\n                    name: \"./resources/verdana.ttf\".to_owned(),\n                    size: 32.0,\n                },\n                color: if trigger {\n                    Color {\n                        r: 1.0,\n                        g: 0.0,\n                        b: 0.0,\n                        a: 1.0,\n                    }\n                } else if selected {\n                    Color {\n                        r: 0.0,\n                        g: 1.0,\n                        b: 0.0,\n                        a: 1.0,\n                    }\n                } else if focused {\n                    Color {\n                        r: 0.0,\n                        g: 0.0,\n                        b: 1.0,\n                        a: 1.0,\n                    }\n                } else {\n                    Color {\n                        r: 0.0,\n                        g: 0.0,\n                        b: 0.0,\n                        a: 1.0,\n                    }\n                },\n                ..Default::default()\n            }),\n        )\n        .into()\n}\n"
  },
  {
    "path": "demos/hello-world/src/ui/mod.rs",
    "content": "pub mod components;\npub mod view_models;\n"
  },
  {
    "path": "demos/hello-world/src/ui/view_models.rs",
    "content": "use raui::core::{\n    Managed,\n    view_model::{ViewModelProperties, ViewModelValue},\n};\n\npub struct AppData {\n    pub input: Managed<ViewModelValue<String>>,\n}\n\nimpl AppData {\n    pub const VIEW_MODEL: &str = \"app-data\";\n    pub const INPUT: &str = \"input\";\n\n    pub fn new(properties: &mut ViewModelProperties) -> Self {\n        Self {\n            input: Managed::new(ViewModelValue::new(\n                Default::default(),\n                properties.notifier(Self::INPUT),\n            )),\n        }\n    }\n}\n"
  },
  {
    "path": "demos/in-game/Cargo.toml",
    "content": "[package]\nname = \"in-game\"\nversion = \"0.1.0\"\nauthors = [\"Patryk 'PsichiX' Budzynski <psichix@gmail.com>\"]\nedition = \"2024\"\npublish = false\n\n[dependencies]\nserde = { version = \"1\", features = [\"derive\"] }\nserde_json = \"1\"\nraui = { version = \"0.70\", path = \"../../crates/_\", features = [\"app\", \"material\"] }\n"
  },
  {
    "path": "demos/in-game/README.md",
    "content": "# In-Game UI demo\n\n## Credits\n- UI icons used: https://crusenho.itch.io/complete-gui-essential-pack"
  },
  {
    "path": "demos/in-game/resources/items.json",
    "content": "{\n    \"potion\": {\n        \"name\": \"Potion\",\n        \"icon\": \"resources/icons/potion.png\",\n        \"buy\": 3,\n        \"sell\": 1\n    },\n    \"sword\": {\n        \"name\": \"Sword\",\n        \"icon\": \"resources/icons/sword.png\",\n        \"buy\": 10,\n        \"sell\": 4\n    },\n    \"shield\": {\n        \"name\": \"Shield\",\n        \"icon\": \"resources/icons/shield.png\",\n        \"buy\": 7,\n        \"sell\": 3\n    }\n}"
  },
  {
    "path": "demos/in-game/resources/quests.json",
    "content": "{\n    \"kill-5-monsters\": {\n        \"name\": \"Kill 5 monsters\"\n    },\n    \"collect-3-potions\": {\n        \"name\": \"Collect 3 potions\"\n    },\n    \"save-the-world\": {\n        \"name\": \"Save the world\"\n    }\n}"
  },
  {
    "path": "demos/in-game/src/main.rs",
    "content": "mod model;\nmod ui;\n\nuse model::{\n    inventory::{Inventory, ItemsDatabase},\n    menu::{Menu, MenuScreen},\n    quests::Quests,\n    settings::Settings,\n};\nuse raui::{\n    app::{\n        app::{App, AppConfig, declarative::DeclarativeApp},\n        event::{ElementState, Event, VirtualKeyCode, WindowEvent},\n    },\n    core::make_widget,\n};\nuse ui::app::app;\n\nfn main() {\n    let app = DeclarativeApp::default()\n        .tree(make_widget!(app))\n        .view_model(Menu::VIEW_MODEL, Menu::view_model())\n        .view_model(Settings::VIEW_MODEL, Settings::view_model())\n        .view_model(\n            Quests::VIEW_MODEL,\n            Quests::view_model(\"resources/quests.json\"),\n        )\n        .view_model(\n            ItemsDatabase::VIEW_MODEL,\n            ItemsDatabase::view_model(\"resources/items.json\"),\n        )\n        .view_model(Inventory::VIEW_MODEL, Inventory::view_model())\n        .event(|app, event, _, _| {\n            if let Event::WindowEvent {\n                event: WindowEvent::KeyboardInput { input, .. },\n                ..\n            } = event\n                && input.state == ElementState::Pressed\n            {\n                match input.virtual_keycode {\n                    Some(VirtualKeyCode::Key1) => {\n                        *app.view_models\n                            .get_mut(Menu::VIEW_MODEL)\n                            .unwrap()\n                            .write::<Menu>()\n                            .unwrap()\n                            .screen = MenuScreen::None;\n                        println!(\"Changed menu: None\");\n                    }\n                    Some(VirtualKeyCode::Key2) => {\n                        *app.view_models\n                            .get_mut(Menu::VIEW_MODEL)\n                            .unwrap()\n                            .write::<Menu>()\n                            .unwrap()\n                            .screen = MenuScreen::Settings;\n                        println!(\"Changed menu: Settings\");\n                    }\n                    Some(VirtualKeyCode::Key3) => {\n                        *app.view_models\n                            .get_mut(Menu::VIEW_MODEL)\n                            .unwrap()\n                            .write::<Menu>()\n                            .unwrap()\n                            .screen = MenuScreen::Quests;\n                        println!(\"Changed menu: Quests\");\n                    }\n                    Some(VirtualKeyCode::Key4) => {\n                        *app.view_models\n                            .get_mut(Menu::VIEW_MODEL)\n                            .unwrap()\n                            .write::<Menu>()\n                            .unwrap()\n                            .screen = MenuScreen::Inventory;\n                        println!(\"Changed menu: Inventory\");\n                    }\n                    Some(VirtualKeyCode::Key5) => {\n                        app.view_models\n                            .get_mut(Inventory::VIEW_MODEL)\n                            .unwrap()\n                            .write::<Inventory>()\n                            .unwrap()\n                            .add(\"potion\", 1);\n                        println!(\"Added item: Potion\");\n                    }\n                    Some(VirtualKeyCode::Key6) => {\n                        app.view_models\n                            .get_mut(Inventory::VIEW_MODEL)\n                            .unwrap()\n                            .write::<Inventory>()\n                            .unwrap()\n                            .add(\"sword\", 1);\n                        println!(\"Added item: Sword\");\n                    }\n                    Some(VirtualKeyCode::Key7) => {\n                        app.view_models\n                            .get_mut(Inventory::VIEW_MODEL)\n                            .unwrap()\n                            .write::<Inventory>()\n                            .unwrap()\n                            .add(\"shield\", 1);\n                        println!(\"Added item: Shield\");\n                    }\n                    Some(VirtualKeyCode::Escape) => {\n                        return false;\n                    }\n                    _ => {}\n                }\n            }\n            true\n        });\n\n    App::new(\n        AppConfig::default()\n            .title(\"In-Game\")\n            .color([0.2, 0.2, 0.2, 1.0]),\n    )\n    .run(app);\n}\n"
  },
  {
    "path": "demos/in-game/src/model/inventory.rs",
    "content": "use raui::core::view_model::{ViewModel, ViewModelValue};\nuse serde::{Deserialize, Serialize};\nuse std::{collections::HashMap, fs::File, path::Path};\n\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct Item {\n    pub name: String,\n    pub icon: String,\n    pub buy: usize,\n    pub sell: usize,\n}\n\npub struct ItemsDatabase {\n    pub items: HashMap<String, Item>,\n}\n\nimpl ItemsDatabase {\n    pub const VIEW_MODEL: &str = \"items-database\";\n\n    pub fn view_model(database_path: impl AsRef<Path>) -> ViewModel {\n        let database_path = database_path.as_ref();\n        let items = File::open(database_path).unwrap_or_else(|err| {\n            panic!(\"Could not load items database: {database_path:?}. Error: {err}\")\n        });\n        let items = serde_json::from_reader(items).unwrap_or_else(|err| {\n            panic!(\"Could not deserialize items database: {database_path:?}. Error: {err}\")\n        });\n\n        ViewModel::new_object(Self { items })\n    }\n}\n\npub struct Inventory {\n    owned: ViewModelValue<HashMap<String, usize>>,\n}\n\nimpl Inventory {\n    pub const VIEW_MODEL: &str = \"items\";\n    pub const OWNED: &str = \"owned\";\n\n    pub fn view_model() -> ViewModel {\n        ViewModel::produce(|properties| {\n            let mut result = Self {\n                owned: ViewModelValue::new(Default::default(), properties.notifier(Self::OWNED)),\n            };\n            result.add(\"potion\", 5);\n            result.add(\"shield\", 1);\n            result.add(\"sword\", 2);\n            result\n        })\n    }\n\n    pub fn add(&mut self, id: impl ToString, count: usize) {\n        let value = self.owned.entry(id.to_string()).or_default();\n        *value = value.saturating_add(count);\n    }\n\n    #[allow(dead_code)]\n    pub fn remove(&mut self, id: &str, count: usize) {\n        if let Some(value) = self.owned.get_mut(id) {\n            *value = value.saturating_sub(count);\n            if *value == 0 {\n                self.owned.remove(id);\n            }\n        }\n    }\n\n    pub fn owned<'a>(\n        &'a self,\n        database: &'a ItemsDatabase,\n    ) -> impl Iterator<Item = (&'a str, usize, &'a Item)> {\n        self.owned\n            .iter()\n            .filter_map(|(id, count)| Some((id.as_str(), *count, database.items.get(id)?)))\n    }\n}\n"
  },
  {
    "path": "demos/in-game/src/model/menu.rs",
    "content": "use raui::core::view_model::{ViewModel, ViewModelValue};\n\n#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]\npub enum MenuScreen {\n    #[default]\n    None,\n    Settings,\n    Inventory,\n    Quests,\n}\n\npub struct Menu {\n    pub screen: ViewModelValue<MenuScreen>,\n}\n\nimpl Menu {\n    pub const VIEW_MODEL: &str = \"menu\";\n    pub const SCREEN: &str = \"screen\";\n\n    pub fn view_model() -> ViewModel {\n        ViewModel::produce(|properties| Self {\n            screen: ViewModelValue::new(Default::default(), properties.notifier(Self::SCREEN)),\n        })\n    }\n}\n"
  },
  {
    "path": "demos/in-game/src/model/mod.rs",
    "content": "pub mod inventory;\npub mod menu;\npub mod quests;\npub mod settings;\n"
  },
  {
    "path": "demos/in-game/src/model/quests.rs",
    "content": "use raui::core::view_model::{ViewModel, ViewModelValue};\nuse serde::{Deserialize, Serialize};\nuse std::{\n    collections::{HashMap, HashSet},\n    fs::File,\n    path::Path,\n};\n\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct Quest {\n    pub name: String,\n}\n\npub struct Quests {\n    database: HashMap<String, Quest>,\n    completed: ViewModelValue<HashSet<String>>,\n}\n\nimpl Quests {\n    pub const VIEW_MODEL: &str = \"quests\";\n    pub const COMPLETED: &str = \"completed\";\n\n    pub fn view_model(database_path: impl AsRef<Path>) -> ViewModel {\n        let database_path = database_path.as_ref();\n        let database = File::open(database_path).unwrap_or_else(|err| {\n            panic!(\"Could not load quests database: {database_path:?}. Error: {err}\")\n        });\n        let database = serde_json::from_reader(database).unwrap_or_else(|err| {\n            panic!(\"Could not deserialize quests database: {database_path:?}. Error: {err}\")\n        });\n\n        ViewModel::produce(|properties| {\n            let mut result = Self {\n                database,\n                completed: ViewModelValue::new(\n                    Default::default(),\n                    properties.notifier(Self::COMPLETED),\n                ),\n            };\n            result.toggle(\"collect-3-potions\");\n            result\n        })\n    }\n\n    pub fn toggle(&mut self, id: impl ToString) {\n        let id = id.to_string();\n        if self.completed.contains(&id) {\n            self.completed.remove(&id);\n        } else {\n            self.completed.insert(id);\n        }\n    }\n\n    pub fn completed(&self) -> impl Iterator<Item = (&str, &Quest)> {\n        let completed = &*self.completed;\n        self.database\n            .iter()\n            .filter(|(id, _)| completed.contains(*id))\n            .map(|(id, quest)| (id.as_str(), quest))\n    }\n\n    pub fn available(&self) -> impl Iterator<Item = (&str, &Quest)> {\n        let completed = &*self.completed;\n        self.database\n            .iter()\n            .filter(|(id, _)| !completed.contains(*id))\n            .map(|(id, quest)| (id.as_str(), quest))\n    }\n}\n"
  },
  {
    "path": "demos/in-game/src/model/settings.rs",
    "content": "use raui::core::{\n    Managed, Scalar,\n    view_model::{ViewModel, ViewModelValue},\n};\n\npub struct Settings {\n    pub fullscreen: ViewModelValue<bool>,\n    pub volume: Managed<ViewModelValue<Scalar>>,\n}\n\nimpl Settings {\n    pub const VIEW_MODEL: &str = \"settings\";\n    const FULLSCREEN: &str = \"fullscreen\";\n    const VOLUME: &str = \"volume\";\n\n    pub fn view_model() -> ViewModel {\n        ViewModel::produce(|properties| Self {\n            fullscreen: ViewModelValue::new(false, properties.notifier(Self::FULLSCREEN)),\n            volume: Managed::new(ViewModelValue::new(\n                100.0,\n                properties.notifier(Self::VOLUME),\n            )),\n        })\n    }\n}\n"
  },
  {
    "path": "demos/in-game/src/ui/app.rs",
    "content": "use super::{inventory::inventory, quests::quests, settings::settings};\nuse crate::model::menu::{Menu, MenuScreen};\nuse raui::{\n    core::{\n        make_widget, pre_hooks,\n        widget::{\n            component::{\n                containers::content_box::content_box,\n                image_box::{ImageBoxProps, image_box},\n            },\n            context::WidgetContext,\n            node::WidgetNode,\n            unit::{\n                image::{\n                    ImageBoxAspectRatio, ImageBoxFrame, ImageBoxImage, ImageBoxImageScaling,\n                    ImageBoxMaterial,\n                },\n                text::{TextBoxFont, TextBoxHorizontalAlign, TextBoxVerticalAlign},\n            },\n            utils::Color,\n        },\n    },\n    material::theme::{\n        ThemeColorSet, ThemeColors, ThemeColorsBundle, ThemeProps, ThemedButtonMaterial,\n        ThemedImageMaterial, ThemedSliderMaterial, ThemedSwitchMaterial, ThemedTextMaterial,\n        new_all_white_theme,\n    },\n};\n\nfn use_app(context: &mut WidgetContext) {\n    context.life_cycle.mount(|mut context| {\n        context\n            .view_models\n            .bindings(Menu::VIEW_MODEL, Menu::SCREEN)\n            .unwrap()\n            .bind(context.id.to_owned());\n    });\n}\n\n#[pre_hooks(use_app)]\npub fn app(mut context: WidgetContext) -> WidgetNode {\n    let menu = context\n        .view_models\n        .view_model(Menu::VIEW_MODEL)\n        .unwrap()\n        .read::<Menu>()\n        .unwrap();\n\n    make_widget!(content_box)\n        .key(\"screen\")\n        .with_shared_props(make_theme())\n        // Let's pretend this image is underlying game world.\n        .listed_slot(\n            make_widget!(image_box)\n                .key(\"game\")\n                .with_props(ImageBoxProps {\n                    material: ImageBoxMaterial::Image(ImageBoxImage {\n                        id: \"resources/images/game-mockup.png\".to_owned(),\n                        ..Default::default()\n                    }),\n                    content_keep_aspect_ratio: Some(ImageBoxAspectRatio {\n                        horizontal_alignment: 0.5,\n                        vertical_alignment: 0.5,\n                        outside: true,\n                    }),\n                    ..Default::default()\n                }),\n        )\n        // And this is our actual game UI screens.\n        .maybe_listed_slot(match *menu.screen {\n            MenuScreen::None => None,\n            MenuScreen::Settings => Some(make_widget!(settings)),\n            MenuScreen::Inventory => Some(make_widget!(inventory)),\n            MenuScreen::Quests => Some(make_widget!(quests)),\n        })\n        .into()\n}\n\nfn make_theme() -> ThemeProps {\n    new_all_white_theme()\n        .background_colors(ThemeColorsBundle::uniform(ThemeColors::uniform(\n            ThemeColorSet::uniform(Color {\n                r: 0.0,\n                g: 0.0,\n                b: 0.0,\n                a: 0.7,\n            }),\n        )))\n        .button_background(\n            \"\",\n            ThemedButtonMaterial {\n                default: ThemedImageMaterial::Image(ImageBoxImage {\n                    id: \"resources/images/button-default.png\".to_owned(),\n                    scaling: ImageBoxImageScaling::Frame(ImageBoxFrame {\n                        source: 6.0.into(),\n                        destination: 6.0.into(),\n                        frame_only: false,\n                        ..Default::default()\n                    }),\n                    ..Default::default()\n                }),\n                selected: ThemedImageMaterial::Image(ImageBoxImage {\n                    id: \"resources/images/button-selected.png\".to_owned(),\n                    scaling: ImageBoxImageScaling::Frame(ImageBoxFrame {\n                        source: 6.0.into(),\n                        destination: 6.0.into(),\n                        frame_only: false,\n                        ..Default::default()\n                    }),\n                    ..Default::default()\n                }),\n                trigger: ThemedImageMaterial::Image(ImageBoxImage {\n                    id: \"resources/images/button-trigger.png\".to_owned(),\n                    scaling: ImageBoxImageScaling::Frame(ImageBoxFrame {\n                        source: 6.0.into(),\n                        destination: 6.0.into(),\n                        frame_only: false,\n                        ..Default::default()\n                    }),\n                    ..Default::default()\n                }),\n            },\n        )\n        .text_variant(\n            \"title\",\n            ThemedTextMaterial {\n                font: TextBoxFont {\n                    name: \"resources/fonts/MiKrollFantasy.ttf\".to_owned(),\n                    size: 64.0,\n                },\n                ..Default::default()\n            },\n        )\n        .text_variant(\n            \"option-label\",\n            ThemedTextMaterial {\n                font: TextBoxFont {\n                    name: \"resources/fonts/MiKrollFantasy.ttf\".to_owned(),\n                    size: 48.0,\n                },\n                ..Default::default()\n            },\n        )\n        .text_variant(\n            \"option-slider\",\n            ThemedTextMaterial {\n                font: TextBoxFont {\n                    name: \"resources/fonts/MiKrollFantasy.ttf\".to_owned(),\n                    size: 32.0,\n                },\n                horizontal_align: TextBoxHorizontalAlign::Center,\n                vertical_align: TextBoxVerticalAlign::Middle,\n                ..Default::default()\n            },\n        )\n        .text_variant(\n            \"tab-label\",\n            ThemedTextMaterial {\n                font: TextBoxFont {\n                    name: \"resources/fonts/MiKrollFantasy.ttf\".to_owned(),\n                    size: 48.0,\n                },\n                horizontal_align: TextBoxHorizontalAlign::Center,\n                vertical_align: TextBoxVerticalAlign::Middle,\n                ..Default::default()\n            },\n        )\n        .text_variant(\n            \"task-name\",\n            ThemedTextMaterial {\n                font: TextBoxFont {\n                    name: \"resources/fonts/MiKrollFantasy.ttf\".to_owned(),\n                    size: 48.0,\n                },\n                horizontal_align: TextBoxHorizontalAlign::Center,\n                vertical_align: TextBoxVerticalAlign::Middle,\n                ..Default::default()\n            },\n        )\n        .text_variant(\n            \"inventory-item-count\",\n            ThemedTextMaterial {\n                font: TextBoxFont {\n                    name: \"resources/fonts/MiKrollFantasy.ttf\".to_owned(),\n                    size: 28.0,\n                },\n                horizontal_align: TextBoxHorizontalAlign::Right,\n                vertical_align: TextBoxVerticalAlign::Bottom,\n                ..Default::default()\n            },\n        )\n        .switch_variant(\n            \"checkbox\",\n            ThemedSwitchMaterial {\n                on: ThemedImageMaterial::Image(ImageBoxImage {\n                    id: \"resources/icons/checkbox-on.png\".to_owned(),\n                    ..Default::default()\n                }),\n                off: ThemedImageMaterial::Image(ImageBoxImage {\n                    id: \"resources/icons/checkbox-off.png\".to_owned(),\n                    ..Default::default()\n                }),\n            },\n        )\n        .slider_variant(\n            \"\",\n            ThemedSliderMaterial {\n                background: ThemedImageMaterial::Image(ImageBoxImage {\n                    id: \"resources/images/slider-background.png\".to_owned(),\n                    scaling: ImageBoxImageScaling::Frame(ImageBoxFrame {\n                        source: 6.0.into(),\n                        destination: 6.0.into(),\n                        frame_only: false,\n                        ..Default::default()\n                    }),\n                    ..Default::default()\n                }),\n                filling: ThemedImageMaterial::Image(ImageBoxImage {\n                    id: \"resources/images/slider-filling.png\".to_owned(),\n                    scaling: ImageBoxImageScaling::Frame(ImageBoxFrame {\n                        source: 6.0.into(),\n                        destination: 6.0.into(),\n                        ..Default::default()\n                    }),\n                    ..Default::default()\n                }),\n            },\n        )\n}\n"
  },
  {
    "path": "demos/in-game/src/ui/inventory.rs",
    "content": "use crate::model::inventory::{Inventory, ItemsDatabase};\nuse raui::{\n    core::{\n        make_widget, pre_hooks,\n        widget::{\n            WidgetIdMetaParams,\n            component::{\n                containers::{\n                    content_box::content_box,\n                    grid_box::GridBoxProps,\n                    size_box::{SizeBoxProps, size_box},\n                },\n                image_box::{ImageBoxProps, image_box},\n                interactive::{\n                    button::{ButtonNotifyMessage, ButtonNotifyProps},\n                    navigation::NavItemActive,\n                },\n            },\n            context::WidgetContext,\n            node::WidgetNode,\n            unit::{\n                content::ContentBoxItemLayout,\n                grid::GridBoxItemLayout,\n                image::{ImageBoxImage, ImageBoxMaterial},\n                size::SizeBoxSizeValue,\n            },\n            utils::{Color, IntRect, Rect, Vec2},\n        },\n    },\n    material::component::{\n        containers::{\n            grid_paper::nav_grid_paper,\n            window_paper::{WindowPaperProps, window_paper},\n        },\n        interactive::button_paper::button_paper,\n        text_paper::{TextPaperProps, text_paper},\n    },\n};\n\n#[pre_hooks(use_inventory)]\npub fn inventory(mut context: WidgetContext) -> WidgetNode {\n    let inventory = context\n        .view_models\n        .view_model(Inventory::VIEW_MODEL)\n        .unwrap()\n        .read::<Inventory>()\n        .unwrap();\n    let database = context\n        .view_models\n        .view_model(ItemsDatabase::VIEW_MODEL)\n        .unwrap()\n        .read::<ItemsDatabase>()\n        .unwrap();\n    let items = inventory\n        .owned(&database)\n        .enumerate()\n        .map(|(index, (id, count, item))| {\n            let col = index as i32 % 5;\n            let row = index as i32 / 5;\n            make_widget!(inventory_item)\n                .key(format!(\"{index}?item={id}\"))\n                .with_props(ButtonNotifyProps(context.id.to_owned().into()))\n                .with_props(GridBoxItemLayout {\n                    space_occupancy: IntRect {\n                        left: col,\n                        right: col + 1,\n                        top: row,\n                        bottom: row + 1,\n                    },\n                    margin: 6.0.into(),\n                    ..Default::default()\n                })\n                .with_props(count)\n                .with_props(item.icon.to_owned())\n        });\n\n    make_widget!(window_paper)\n        .key(\"inventory\")\n        .with_props(WindowPaperProps {\n            bar_margin: 20.0.into(),\n            bar_height: Some(80.0),\n            content_margin: 40.0.into(),\n            ..Default::default()\n        })\n        .named_slot(\n            \"bar\",\n            make_widget!(text_paper)\n                .key(\"title\")\n                .with_props(TextPaperProps {\n                    text: \"INVENTORY\".to_owned(),\n                    variant: \"title\".to_owned(),\n                    use_main_color: true,\n                    ..Default::default()\n                }),\n        )\n        .named_slot(\n            \"content\",\n            make_widget!(content_box).listed_slot(\n                make_widget!(size_box)\n                    .with_props(ContentBoxItemLayout {\n                        anchors: Rect {\n                            left: 0.5,\n                            right: 0.5,\n                            top: 0.5,\n                            bottom: 0.5,\n                        },\n                        align: Vec2 { x: 0.5, y: 0.5 },\n                        ..Default::default()\n                    })\n                    .with_props(SizeBoxProps {\n                        width: SizeBoxSizeValue::Exact(400.0),\n                        height: SizeBoxSizeValue::Exact(400.0),\n                        ..Default::default()\n                    })\n                    .named_slot(\n                        \"content\",\n                        make_widget!(nav_grid_paper)\n                            .with_props(GridBoxProps {\n                                cols: 5,\n                                rows: 5,\n                                ..Default::default()\n                            })\n                            .listed_slots(items),\n                    ),\n            ),\n        )\n        .into()\n}\n\nfn inventory_item(context: WidgetContext) -> WidgetNode {\n    let WidgetContext { key, props, .. } = context;\n\n    let notify = props.read_cloned::<ButtonNotifyProps>().ok();\n    let icon = props.read_cloned_or_default::<String>();\n    let count = props.read_cloned_or_default::<usize>();\n\n    make_widget!(button_paper)\n        .key(key)\n        .with_props(NavItemActive)\n        .maybe_with_props(notify)\n        .named_slot(\n            \"content\",\n            make_widget!(content_box)\n                .key(key)\n                .listed_slot(\n                    make_widget!(image_box)\n                        .key(\"icon\")\n                        .with_props(ContentBoxItemLayout {\n                            margin: 16.0.into(),\n                            ..Default::default()\n                        })\n                        .with_props(ImageBoxProps {\n                            material: ImageBoxMaterial::Image(ImageBoxImage {\n                                id: icon,\n                                tint: Color {\n                                    r: 0.0,\n                                    g: 0.0,\n                                    b: 0.0,\n                                    a: 1.0,\n                                },\n                                ..Default::default()\n                            }),\n                            ..Default::default()\n                        }),\n                )\n                .listed_slot(\n                    make_widget!(text_paper)\n                        .with_props(ContentBoxItemLayout {\n                            margin: 2.0.into(),\n                            ..Default::default()\n                        })\n                        .with_props(TextPaperProps {\n                            text: count.to_string(),\n                            variant: \"inventory-item-count\".to_owned(),\n                            ..Default::default()\n                        }),\n                ),\n        )\n        .into()\n}\n\nfn use_inventory(context: &mut WidgetContext) {\n    context.life_cycle.mount(|mut context| {\n        context\n            .view_models\n            .bindings(Inventory::VIEW_MODEL, Inventory::OWNED)\n            .unwrap()\n            .bind(context.id.to_owned());\n    });\n\n    context.life_cycle.change(|mut context| {\n        let mut inventory = context\n            .view_models\n            .view_model_mut(Inventory::VIEW_MODEL)\n            .unwrap()\n            .write::<Inventory>()\n            .unwrap();\n\n        for msg in context.messenger.messages {\n            if let Some(msg) = msg.as_any().downcast_ref::<ButtonNotifyMessage>()\n                && let Some(id) = WidgetIdMetaParams::new(msg.sender.meta()).find_value(\"item\")\n                && msg.trigger_start()\n            {\n                inventory.remove(id, 1);\n            }\n        }\n    });\n}\n"
  },
  {
    "path": "demos/in-game/src/ui/mod.rs",
    "content": "pub mod app;\npub mod inventory;\npub mod quests;\npub mod settings;\n"
  },
  {
    "path": "demos/in-game/src/ui/quests.rs",
    "content": "use crate::model::quests::{Quest, Quests};\nuse raui::{\n    core::{\n        make_widget, pre_hooks,\n        widget::{\n            WidgetIdMetaParams,\n            component::{\n                containers::{\n                    content_box::content_box,\n                    tabs_box::{TabPlateProps, TabsBoxProps, nav_tabs_box},\n                    vertical_box::{VerticalBoxProps, vertical_box},\n                },\n                image_box::{ImageBoxProps, image_box},\n                interactive::{\n                    button::{ButtonNotifyMessage, ButtonNotifyProps},\n                    navigation::NavItemActive,\n                },\n            },\n            context::WidgetContext,\n            node::WidgetNode,\n            unit::flex::FlexBoxItemLayout,\n        },\n    },\n    material::component::{\n        containers::window_paper::{WindowPaperProps, window_paper},\n        interactive::text_button_paper::text_button_paper,\n        text_paper::{TextPaperProps, text_paper},\n    },\n};\n\n#[pre_hooks(use_quests)]\npub fn quests(mut context: WidgetContext) -> WidgetNode {\n    make_widget!(window_paper)\n        .key(\"quests\")\n        .with_props(WindowPaperProps {\n            bar_margin: 20.0.into(),\n            bar_height: Some(80.0),\n            content_margin: 40.0.into(),\n            ..Default::default()\n        })\n        .named_slot(\n            \"bar\",\n            make_widget!(text_paper)\n                .key(\"title\")\n                .with_props(TextPaperProps {\n                    text: \"QUESTS\".to_owned(),\n                    variant: \"title\".to_owned(),\n                    use_main_color: true,\n                    ..Default::default()\n                }),\n        )\n        .named_slot(\n            \"content\",\n            make_widget!(nav_tabs_box)\n                .key(\"tabs\")\n                .with_props(NavItemActive)\n                .with_props(TabsBoxProps {\n                    tabs_basis: Some(64.0),\n                    tabs_and_content_separation: 20.0,\n                    ..Default::default()\n                })\n                .listed_slot([\n                    make_widget!(tab_plate)\n                        .with_props(\"AVAILABLE\".to_owned())\n                        .into(),\n                    make_widget!(available_tasks)\n                        .with_props(ButtonNotifyProps(context.id.to_owned().into()))\n                        .into(),\n                ])\n                .listed_slot([\n                    make_widget!(tab_plate)\n                        .with_props(\"COMPLETED\".to_owned())\n                        .into(),\n                    make_widget!(completed_tasks)\n                        .with_props(ButtonNotifyProps(context.id.to_owned().into()))\n                        .into(),\n                ]),\n        )\n        .into()\n}\n\nfn tab_plate(context: WidgetContext) -> WidgetNode {\n    let WidgetContext { props, .. } = context;\n    let active = props.read_cloned_or_default::<TabPlateProps>().active;\n    let text = props.read_cloned_or_default::<String>();\n\n    make_widget!(content_box)\n        .key(\"plate\")\n        .maybe_listed_slot(active.then(|| {\n            make_widget!(image_box)\n                .key(\"background\")\n                .with_props(ImageBoxProps::colored(Default::default()))\n        }))\n        .listed_slot(\n            make_widget!(text_paper)\n                .key(\"text\")\n                .with_props(TextPaperProps {\n                    text,\n                    variant: \"tab-label\".to_owned(),\n                    use_main_color: !active,\n                    ..Default::default()\n                }),\n        )\n        .into()\n}\n\nfn available_tasks(context: WidgetContext) -> WidgetNode {\n    let quests = context\n        .view_models\n        .view_model(Quests::VIEW_MODEL)\n        .unwrap()\n        .read::<Quests>()\n        .unwrap();\n    let notify = context.props.read_cloned_or_default::<ButtonNotifyProps>();\n\n    make_tasks_list(notify, \"available\", quests.available())\n}\n\nfn completed_tasks(context: WidgetContext) -> WidgetNode {\n    let quests = context\n        .view_models\n        .view_model(Quests::VIEW_MODEL)\n        .unwrap()\n        .read::<Quests>()\n        .unwrap();\n    let notify = context.props.read_cloned_or_default::<ButtonNotifyProps>();\n\n    make_tasks_list(notify, \"completed\", quests.completed())\n}\n\nfn make_tasks_list<'a>(\n    notify: ButtonNotifyProps,\n    key: &str,\n    tasks: impl Iterator<Item = (&'a str, &'a Quest)>,\n) -> WidgetNode {\n    make_widget!(vertical_box)\n        .key(key)\n        .with_props(VerticalBoxProps {\n            override_slots_layout: Some(FlexBoxItemLayout {\n                basis: Some(48.0),\n                grow: 0.0,\n                shrink: 0.0,\n                ..Default::default()\n            }),\n            separation: 10.0,\n            ..Default::default()\n        })\n        .listed_slots(tasks.enumerate().map(|(index, (id, task))| {\n            make_widget!(quest_task)\n                .key(format!(\"{index}?item={id}\"))\n                .with_props(task.name.to_owned())\n                .with_props(notify.to_owned())\n        }))\n        .into()\n}\n\nfn quest_task(context: WidgetContext) -> WidgetNode {\n    let WidgetContext { key, props, .. } = context;\n\n    let notify = props.read_cloned::<ButtonNotifyProps>().ok();\n    let name = props.read_cloned_or_default::<String>();\n\n    make_widget!(text_button_paper)\n        .key(key)\n        .maybe_with_props(notify)\n        .with_props(NavItemActive)\n        .with_props(TextPaperProps {\n            text: name,\n            variant: \"task-name\".to_owned(),\n            ..Default::default()\n        })\n        .into()\n}\n\nfn use_quests(context: &mut WidgetContext) {\n    context.life_cycle.mount(|mut context| {\n        context\n            .view_models\n            .bindings(Quests::VIEW_MODEL, Quests::COMPLETED)\n            .unwrap()\n            .bind(context.id.to_owned());\n    });\n\n    context.life_cycle.change(|mut context| {\n        let mut quests = context\n            .view_models\n            .view_model_mut(Quests::VIEW_MODEL)\n            .unwrap()\n            .write::<Quests>()\n            .unwrap();\n\n        for msg in context.messenger.messages {\n            if let Some(msg) = msg.as_any().downcast_ref::<ButtonNotifyMessage>()\n                && let Some(id) = WidgetIdMetaParams::new(msg.sender.meta()).find_value(\"item\")\n                && msg.trigger_start()\n            {\n                quests.toggle(id);\n            }\n        }\n    });\n}\n"
  },
  {
    "path": "demos/in-game/src/ui/settings.rs",
    "content": "use crate::model::settings::Settings;\nuse raui::{\n    core::{\n        make_widget, pre_hooks, unpack_named_slots,\n        widget::{\n            WidgetIdMetaParams,\n            component::{\n                containers::{\n                    horizontal_box::{HorizontalBoxProps, horizontal_box},\n                    vertical_box::{VerticalBoxProps, nav_vertical_box},\n                },\n                interactive::{\n                    button::{ButtonNotifyMessage, ButtonNotifyProps},\n                    navigation::NavItemActive,\n                    slider_view::{SliderInput, SliderViewDirection, SliderViewProps},\n                },\n            },\n            context::WidgetContext,\n            node::WidgetNode,\n            unit::{flex::FlexBoxItemLayout, text::TextBoxHorizontalAlign},\n        },\n    },\n    material::{\n        component::{\n            containers::window_paper::{WindowPaperProps, window_paper},\n            interactive::{\n                slider_paper::{NumericSliderPaperProps, numeric_slider_paper},\n                switch_button_paper::switch_button_paper,\n            },\n            switch_paper::SwitchPaperProps,\n            text_paper::{TextPaperProps, text_paper},\n        },\n        theme::{ThemeColor, ThemeVariant, ThemedWidgetProps},\n    },\n};\n\n#[pre_hooks(use_settings)]\npub fn settings(mut context: WidgetContext) -> WidgetNode {\n    let mut settings = context\n        .view_models\n        .view_model_mut(Settings::VIEW_MODEL)\n        .unwrap()\n        .write::<Settings>()\n        .unwrap();\n\n    make_widget!(window_paper)\n        .key(\"settings\")\n        .with_props(WindowPaperProps {\n            bar_margin: 20.0.into(),\n            bar_height: Some(80.0),\n            content_margin: 40.0.into(),\n            ..Default::default()\n        })\n        .named_slot(\n            \"bar\",\n            make_widget!(text_paper)\n                .key(\"title\")\n                .with_props(TextPaperProps {\n                    text: \"SETTINGS\".to_owned(),\n                    variant: \"title\".to_owned(),\n                    use_main_color: true,\n                    ..Default::default()\n                }),\n        )\n        .named_slot(\n            \"content\",\n            make_widget!(nav_vertical_box)\n                .key(\"options\")\n                .with_props(VerticalBoxProps {\n                    override_slots_layout: Some(FlexBoxItemLayout {\n                        grow: 0.0,\n                        shrink: 0.0,\n                        basis: Some(48.0),\n                        ..Default::default()\n                    }),\n                    ..Default::default()\n                })\n                .listed_slot(\n                    make_widget!(option)\n                        .key(\"fullscreen\")\n                        .with_props(\"Fullscreen\".to_owned())\n                        .named_slot(\n                            \"content\",\n                            make_widget!(switch_button_paper)\n                                .key(\"button?id=fullscreen\")\n                                .with_props(NavItemActive)\n                                .with_props(ButtonNotifyProps(context.id.to_owned().into()))\n                                .with_props(SwitchPaperProps {\n                                    on: *settings.fullscreen,\n                                    variant: \"checkbox\".to_owned(),\n                                    size_level: 3,\n                                })\n                                .with_props(ThemedWidgetProps {\n                                    color: ThemeColor::Primary,\n                                    variant: ThemeVariant::ContentOnly,\n                                    ..Default::default()\n                                }),\n                        ),\n                )\n                .listed_slot(\n                    make_widget!(option)\n                        .key(\"volume\")\n                        .with_props(\"Volume\".to_owned())\n                        .named_slot(\n                            \"content\",\n                            make_widget!(numeric_slider_paper)\n                                .key(\"slider\")\n                                .with_props(NavItemActive)\n                                .with_props(TextPaperProps {\n                                    variant: \"option-slider\".to_owned(),\n                                    use_main_color: true,\n                                    ..Default::default()\n                                })\n                                .with_props(SliderViewProps {\n                                    input: Some(SliderInput::new(settings.volume.lazy())),\n                                    from: 0.0,\n                                    to: 100.0,\n                                    direction: SliderViewDirection::LeftToRight,\n                                })\n                                .with_props(NumericSliderPaperProps {\n                                    fractional_digits_count: Some(0),\n                                }),\n                        ),\n                ),\n        )\n        .into()\n}\n\nfn use_settings(context: &mut WidgetContext) {\n    context.life_cycle.change(|mut context| {\n        for msg in context.messenger.messages {\n            if let Some(msg) = msg.as_any().downcast_ref::<ButtonNotifyMessage>()\n                && msg.trigger_start()\n                && let Some(id) = WidgetIdMetaParams::new(msg.sender.meta()).find_value(\"id\")\n                && id == \"fullscreen\"\n            {\n                let mut settings = context\n                    .view_models\n                    .view_model_mut(Settings::VIEW_MODEL)\n                    .unwrap()\n                    .write::<Settings>()\n                    .unwrap();\n                *settings.fullscreen = !*settings.fullscreen;\n            }\n        }\n    });\n}\n\nfn option(context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        key,\n        props,\n        named_slots,\n        ..\n    } = context;\n\n    unpack_named_slots!(named_slots => { content });\n    let label = props.read_cloned_or_default::<String>();\n\n    make_widget!(horizontal_box)\n        .key(key)\n        .with_props(HorizontalBoxProps {\n            separation: 50.0,\n            ..Default::default()\n        })\n        .listed_slot(\n            make_widget!(text_paper)\n                .key(\"label\")\n                .with_props(TextPaperProps {\n                    text: label,\n                    variant: \"option-label\".to_owned(),\n                    use_main_color: true,\n                    horizontal_align_override: Some(TextBoxHorizontalAlign::Right),\n                    ..Default::default()\n                }),\n        )\n        .listed_slot(content)\n        .into()\n}\n"
  },
  {
    "path": "demos/todo-app/.gitignore",
    "content": "/state.json\n*.log\n"
  },
  {
    "path": "demos/todo-app/Cargo.toml",
    "content": "[package]\nname = \"todo-app\"\nversion = \"0.1.0\"\nauthors = [\"Patryk 'PsichiX' Budzynski <psichix@gmail.com>\"]\nedition = \"2024\"\npublish = false\n\n[dependencies]\nserde = { version = \"1\", features = [\"derive\"] }\nserde_json = \"1\"\nraui = { version = \"0.70\", path = \"../../crates/_\", features = [\"app\", \"material\"] }\n"
  },
  {
    "path": "demos/todo-app/resources/fonts/Roboto/LICENSE.txt",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "demos/todo-app/src/main.rs",
    "content": "mod model;\nmod ui;\n\nuse crate::{model::AppState, ui::components::app::app};\nuse raui::{\n    app::app::{App, AppConfig, declarative::DeclarativeApp},\n    core::{make_widget, view_model::ViewModel},\n};\n\nfn main() {\n    let app = DeclarativeApp::default()\n        .tree(make_widget!(app))\n        .view_model(\n            AppState::VIEW_MODEL,\n            ViewModel::produce(|properties| {\n                let mut result = AppState::new(properties);\n                result.load();\n                result\n            }),\n        );\n\n    App::new(AppConfig::default().title(\"TODO App\")).run(app);\n}\n"
  },
  {
    "path": "demos/todo-app/src/model.rs",
    "content": "use raui::core::{\n    Managed, ManagedLazy, Prefab, PropsData,\n    props::PropsData,\n    view_model::{ViewModelProperties, ViewModelValue},\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]\npub enum ThemeMode {\n    Light,\n    #[default]\n    Dark,\n}\n\nimpl ThemeMode {\n    pub fn toggle(&mut self) {\n        *self = match *self {\n            Self::Dark => Self::Light,\n            Self::Light => Self::Dark,\n        }\n    }\n}\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\npub struct TaskProps {\n    #[serde(default)]\n    pub done: bool,\n    #[serde(default)]\n    pub name: String,\n}\n\nimpl TaskProps {\n    pub fn new(name: impl ToString) -> Self {\n        Self {\n            done: false,\n            name: name.to_string(),\n        }\n    }\n}\n\n#[derive(Debug, Default, Clone, Serialize, Deserialize)]\nstruct AppStateSave {\n    theme: ThemeMode,\n    tasks: Vec<TaskProps>,\n}\n\npub struct AppState {\n    theme: ViewModelValue<ThemeMode>,\n    tasks: ViewModelValue<Vec<TaskProps>>,\n    creating_task: ViewModelValue<bool>,\n    new_task_name: Managed<ViewModelValue<String>>,\n}\n\nimpl AppState {\n    pub const VIEW_MODEL: &str = \"app-state\";\n    pub const THEME: &str = \"theme\";\n    pub const TASKS: &str = \"tasks\";\n    pub const CREATING_TASK: &str = \"creating-task\";\n    pub const NEW_TASK_NAME: &str = \"new-task-name\";\n\n    pub fn new(properties: &mut ViewModelProperties) -> Self {\n        Self {\n            theme: ViewModelValue::new(ThemeMode::Dark, properties.notifier(Self::THEME)),\n            tasks: ViewModelValue::new(Default::default(), properties.notifier(Self::TASKS)),\n            creating_task: ViewModelValue::new(false, properties.notifier(Self::CREATING_TASK)),\n            new_task_name: Managed::new(ViewModelValue::new(\n                Default::default(),\n                properties.notifier(Self::NEW_TASK_NAME),\n            )),\n        }\n    }\n\n    pub fn theme(&self) -> ThemeMode {\n        *self.theme\n    }\n\n    pub fn tasks(&self) -> impl Iterator<Item = &TaskProps> {\n        self.tasks.iter()\n    }\n\n    pub fn toggle_theme(&mut self) {\n        self.theme.toggle();\n    }\n\n    pub fn creating_task(&self) -> bool {\n        *self.creating_task\n    }\n\n    pub fn new_task_name(&mut self) -> ManagedLazy<ViewModelValue<String>> {\n        self.new_task_name.lazy()\n    }\n\n    pub fn create_task(&mut self) {\n        *self.creating_task = true;\n        **self.new_task_name.write().unwrap() = Default::default();\n    }\n\n    pub fn add_task(&mut self) {\n        if *self.creating_task {\n            *self.creating_task = false;\n            let name = std::mem::take(&mut **self.new_task_name.write().unwrap());\n            if !name.is_empty() {\n                self.tasks.push(TaskProps::new(name));\n            }\n        }\n    }\n\n    pub fn delete_task(&mut self, index: usize) {\n        if index < self.tasks.len() {\n            self.tasks.remove(index);\n        }\n    }\n\n    pub fn toggle_task(&mut self, index: usize) {\n        if let Some(task) = self.tasks.get_mut(index) {\n            task.done = !task.done;\n        }\n    }\n\n    pub fn load(&mut self) {\n        if let Ok(content) = std::fs::read_to_string(\"./state.json\")\n            && let Ok(state) = serde_json::from_str::<AppStateSave>(&content)\n        {\n            *self.theme = state.theme;\n            *self.tasks = state.tasks;\n        }\n    }\n\n    pub fn save(&self) {\n        let state = AppStateSave {\n            theme: self.theme.to_owned(),\n            tasks: self.tasks.iter().cloned().collect(),\n        };\n        if let Ok(content) = serde_json::to_string_pretty(&state) {\n            let _ = std::fs::write(\"./state.json\", content);\n        }\n    }\n}\n"
  },
  {
    "path": "demos/todo-app/src/ui/components/app.rs",
    "content": "use super::{app_bar::app_bar, tasks_list::tasks_list};\nuse crate::model::{AppState, ThemeMode};\nuse raui::{\n    core::{\n        make_widget, pre_hooks,\n        widget::{\n            WidgetRef,\n            component::{\n                containers::{\n                    portal_box::PortalsContainer,\n                    vertical_box::{VerticalBoxProps, vertical_box},\n                    wrap_box::{WrapBoxProps, wrap_box},\n                },\n                interactive::navigation::use_nav_container_active,\n            },\n            context::WidgetContext,\n            node::WidgetNode,\n            unit::{\n                flex::FlexBoxItemLayout,\n                image::ImageBoxImage,\n                text::{TextBoxFont, TextBoxHorizontalAlign, TextBoxVerticalAlign},\n            },\n        },\n    },\n    material::{\n        component::containers::paper::paper,\n        theme::{\n            ThemeProps, ThemedImageMaterial, ThemedSwitchMaterial, ThemedTextMaterial,\n            new_dark_theme, new_light_theme,\n        },\n    },\n};\n\nfn new_theme(theme: ThemeMode) -> ThemeProps {\n    let mut theme = match theme {\n        ThemeMode::Light => new_light_theme(),\n        ThemeMode::Dark => new_dark_theme(),\n    };\n    theme.text_variants.insert(\n        \"title\".to_owned(),\n        ThemedTextMaterial {\n            font: TextBoxFont {\n                name: \"resources/fonts/Roboto/Roboto-Black.ttf\".to_owned(),\n                size: 24.0,\n            },\n            vertical_align: TextBoxVerticalAlign::Middle,\n            ..Default::default()\n        },\n    );\n    theme.text_variants.insert(\n        \"input\".to_owned(),\n        ThemedTextMaterial {\n            font: TextBoxFont {\n                name: \"resources/fonts/Roboto/Roboto-Regular.ttf\".to_owned(),\n                size: 24.0,\n            },\n            ..Default::default()\n        },\n    );\n    theme.text_variants.insert(\n        \"tooltip\".to_owned(),\n        ThemedTextMaterial {\n            font: TextBoxFont {\n                name: \"resources/fonts/Roboto/Roboto-BoldItalic.ttf\".to_owned(),\n                size: 18.0,\n            },\n            ..Default::default()\n        },\n    );\n    theme.text_variants.insert(\n        \"button\".to_owned(),\n        ThemedTextMaterial {\n            font: TextBoxFont {\n                name: \"resources/fonts/Roboto/Roboto-Bold.ttf\".to_owned(),\n                size: 24.0,\n            },\n            horizontal_align: TextBoxHorizontalAlign::Center,\n            vertical_align: TextBoxVerticalAlign::Middle,\n            ..Default::default()\n        },\n    );\n    theme.switch_variants.insert(\n        \"checkbox\".to_owned(),\n        ThemedSwitchMaterial {\n            on: ThemedImageMaterial::Image(ImageBoxImage {\n                id: \"resources/icons/check-box-on.png\".to_owned(),\n                ..Default::default()\n            }),\n            off: ThemedImageMaterial::Image(ImageBoxImage {\n                id: \"resources/icons/check-box-off.png\".to_owned(),\n                ..Default::default()\n            }),\n        },\n    );\n    theme\n}\n\nfn use_app(context: &mut WidgetContext) {\n    context.life_cycle.mount(|mut context| {\n        context\n            .view_models\n            .bindings(AppState::VIEW_MODEL, AppState::THEME)\n            .unwrap()\n            .bind(context.id.to_owned());\n    });\n}\n\n#[pre_hooks(use_nav_container_active, use_app)]\npub fn app(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        key, view_models, ..\n    } = context;\n    let app_state = view_models\n        .view_model(AppState::VIEW_MODEL)\n        .unwrap()\n        .read::<AppState>()\n        .unwrap();\n    let idref = WidgetRef::default();\n\n    make_widget!(paper)\n        .key(key)\n        .idref(idref.clone())\n        .with_shared_props(PortalsContainer(idref.clone()))\n        .with_shared_props(new_theme(app_state.theme()))\n        .listed_slot(\n            make_widget!(wrap_box)\n                .key(\"wrap\")\n                .with_props(WrapBoxProps {\n                    margin: 32.0.into(),\n                    ..Default::default()\n                })\n                .named_slot(\n                    \"content\",\n                    make_widget!(vertical_box)\n                        .key(\"list\")\n                        .with_props(VerticalBoxProps {\n                            separation: 10.0,\n                            ..Default::default()\n                        })\n                        .listed_slot(make_widget!(app_bar).key(\"app-bar\").with_props(\n                            FlexBoxItemLayout {\n                                grow: 0.0,\n                                shrink: 0.0,\n                                ..Default::default()\n                            },\n                        ))\n                        .listed_slot(make_widget!(tasks_list).key(\"tasks-list\")),\n                ),\n        )\n        .into()\n}\n"
  },
  {
    "path": "demos/todo-app/src/ui/components/app_bar.rs",
    "content": "use crate::model::{AppState, ThemeMode};\nuse raui::{\n    core::{\n        make_widget, pre_hooks,\n        props::Props,\n        widget::{\n            WidgetId,\n            component::{\n                containers::{\n                    anchor_box::PivotBoxProps,\n                    horizontal_box::{HorizontalBoxProps, horizontal_box},\n                    vertical_box::{VerticalBoxProps, vertical_box},\n                },\n                interactive::{\n                    button::{ButtonNotifyMessage, ButtonNotifyProps},\n                    input_field::TextInputProps,\n                    navigation::NavItemActive,\n                },\n            },\n            context::WidgetContext,\n            node::WidgetNode,\n            unit::{flex::FlexBoxItemLayout, text::TextBoxSizeValue},\n            utils::{Rect, Vec2},\n        },\n    },\n    material::{\n        component::{\n            containers::text_tooltip_paper::text_tooltip_paper,\n            icon_paper::{IconImage, IconPaperProps},\n            interactive::{\n                icon_button_paper::icon_button_paper,\n                text_field_paper::{TextFieldPaperProps, text_field_paper},\n            },\n            text_paper::{TextPaperProps, text_paper},\n        },\n        theme::{ThemeColor, ThemeVariant, ThemedWidgetProps},\n    },\n};\n\nfn use_app_bar(context: &mut WidgetContext) {\n    context.life_cycle.change(|mut context| {\n        let mut app_state = context\n            .view_models\n            .view_model_mut(AppState::VIEW_MODEL)\n            .unwrap()\n            .write::<AppState>()\n            .unwrap();\n\n        for msg in context.messenger.messages {\n            if let Some(msg) = msg.as_any().downcast_ref::<ButtonNotifyMessage>()\n                && msg.trigger_start()\n            {\n                match msg.sender.key() {\n                    \"theme\" => {\n                        app_state.toggle_theme();\n                    }\n                    \"save\" => {\n                        app_state.save();\n                    }\n                    \"create\" => {\n                        app_state.create_task();\n                    }\n                    \"add\" => {\n                        app_state.add_task();\n                    }\n                    _ => {}\n                }\n            }\n        }\n    });\n}\n\n#[pre_hooks(use_app_bar)]\npub fn app_bar(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        key,\n        id,\n        mut view_models,\n        ..\n    } = context;\n    let mut app_state = view_models\n        .view_model_mut(AppState::VIEW_MODEL)\n        .unwrap()\n        .write::<AppState>()\n        .unwrap();\n\n    make_widget!(vertical_box)\n        .key(key)\n        .with_props(VerticalBoxProps {\n            separation: 10.0,\n            ..Default::default()\n        })\n        .listed_slot(\n            make_widget!(horizontal_box)\n                .key(\"title-bar\")\n                .with_props(HorizontalBoxProps {\n                    separation: 10.0,\n                    ..Default::default()\n                })\n                .listed_slot(\n                    make_widget!(text_paper)\n                        .key(\"title\")\n                        .with_props(TextPaperProps {\n                            text: \"TODO App\".to_owned(),\n                            variant: \"title\".to_owned(),\n                            ..Default::default()\n                        }),\n                )\n                .listed_slot(\n                    make_widget!(text_tooltip_paper)\n                        .merge_props(make_tooltip_props(\"Change theme\"))\n                        .named_slot(\n                            \"content\",\n                            make_widget!(icon_button_paper).key(\"theme\").merge_props(\n                                make_icon_props(\n                                    id,\n                                    if app_state.theme() == ThemeMode::Dark {\n                                        \"resources/icons/light-mode.png\"\n                                    } else {\n                                        \"resources/icons/dark-mode.png\"\n                                    },\n                                ),\n                            ),\n                        ),\n                )\n                .listed_slot(\n                    make_widget!(text_tooltip_paper)\n                        .merge_props(make_tooltip_props(\"Save changes\"))\n                        .named_slot(\n                            \"content\",\n                            make_widget!(icon_button_paper)\n                                .key(\"save\")\n                                .merge_props(make_icon_props(id, \"resources/icons/save.png\")),\n                        ),\n                )\n                .listed_slot(if app_state.creating_task() {\n                    WidgetNode::default()\n                } else {\n                    make_widget!(text_tooltip_paper)\n                        .merge_props(make_tooltip_props(\"Create task\"))\n                        .named_slot(\n                            \"content\",\n                            make_widget!(icon_button_paper)\n                                .key(\"create\")\n                                .merge_props(make_icon_props(id, \"resources/icons/add.png\")),\n                        )\n                        .into()\n                }),\n        )\n        .listed_slot(if app_state.creating_task() {\n            make_widget!(horizontal_box)\n                .key(\"task-bar\")\n                .with_props(HorizontalBoxProps {\n                    separation: 10.0,\n                    ..Default::default()\n                })\n                .listed_slot(\n                    make_widget!(text_field_paper)\n                        .key(\"name\")\n                        .with_props(TextFieldPaperProps {\n                            hint: \"> Type new task name...\".to_owned(),\n                            paper_theme: ThemedWidgetProps {\n                                color: ThemeColor::Primary,\n                                ..Default::default()\n                            },\n                            padding: Rect {\n                                left: 10.0,\n                                right: 10.0,\n                                top: 6.0,\n                                bottom: 6.0,\n                            },\n                            variant: \"input\".to_owned(),\n                            ..Default::default()\n                        })\n                        .with_props(NavItemActive)\n                        .with_props(ButtonNotifyProps(id.to_owned().into()))\n                        .with_props(TextInputProps {\n                            text: Some(app_state.new_task_name().into()),\n                            ..Default::default()\n                        }),\n                )\n                .listed_slot(\n                    make_widget!(text_tooltip_paper)\n                        .merge_props(make_tooltip_props(\"Confirm new task\"))\n                        .named_slot(\n                            \"content\",\n                            make_widget!(icon_button_paper)\n                                .key(\"add\")\n                                .merge_props(make_icon_props(id, \"resources/icons/add.png\")),\n                        ),\n                )\n                .into()\n        } else {\n            WidgetNode::default()\n        })\n        .into()\n}\n\nfn make_tooltip_props(hint: &str) -> Props {\n    Props::new(FlexBoxItemLayout {\n        fill: 0.0,\n        grow: 0.0,\n        shrink: 0.0,\n        align: 0.5,\n        ..Default::default()\n    })\n    .with(PivotBoxProps {\n        pivot: Vec2 { x: 1.0, y: 1.0 },\n        align: Vec2 { x: 1.0, y: 0.0 },\n    })\n    .with(TextPaperProps {\n        text: hint.to_owned(),\n        width: TextBoxSizeValue::Exact(150.0),\n        height: TextBoxSizeValue::Exact(24.0),\n        variant: \"tooltip\".to_owned(),\n        ..Default::default()\n    })\n}\n\nfn make_icon_props(id: &WidgetId, image_id: impl ToString) -> Props {\n    Props::new(IconPaperProps {\n        image: IconImage {\n            id: image_id.to_string(),\n            ..Default::default()\n        },\n        size_level: 2,\n        ..Default::default()\n    })\n    .with(ThemedWidgetProps {\n        color: ThemeColor::Secondary,\n        variant: ThemeVariant::ContentOnly,\n        ..Default::default()\n    })\n    .with(NavItemActive)\n    .with(ButtonNotifyProps(id.to_owned().into()))\n}\n"
  },
  {
    "path": "demos/todo-app/src/ui/components/confirm_box.rs",
    "content": "use raui::{\n    core::{\n        MessageData, Prefab, PropsData, make_widget,\n        messenger::MessageData,\n        pre_hooks,\n        props::PropsData,\n        widget::{\n            WidgetId, WidgetIdOrRef,\n            component::{\n                containers::{\n                    horizontal_box::horizontal_box,\n                    vertical_box::VerticalBoxProps,\n                    wrap_box::{WrapBoxProps, wrap_box},\n                },\n                interactive::{\n                    button::{ButtonNotifyMessage, ButtonNotifyProps},\n                    navigation::NavItemActive,\n                },\n            },\n            context::WidgetContext,\n            node::WidgetNode,\n            unit::{content::ContentBoxItemLayout, text::TextBoxSizeValue},\n            utils::Rect,\n        },\n    },\n    material::component::{\n        containers::{modal_paper::modal_paper, vertical_paper::vertical_paper},\n        interactive::text_button_paper::text_button_paper,\n        text_paper::{TextPaperProps, text_paper},\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]\npub struct ConfirmBoxProps {\n    #[serde(default)]\n    pub text: String,\n    #[serde(default)]\n    pub notify: WidgetIdOrRef,\n}\n\n#[derive(MessageData, Debug, Clone)]\npub struct ConfirmNotifyMessage {\n    #[allow(dead_code)]\n    pub sender: WidgetId,\n    pub confirmed: bool,\n}\n\nfn use_confirm_box(context: &mut WidgetContext) {\n    let notify = context\n        .props\n        .map_or_default::<ConfirmBoxProps, _, _>(|p| p.notify.to_owned());\n    let notify = match notify.read() {\n        Some(id) => id,\n        None => return,\n    };\n\n    context.life_cycle.change(move |context| {\n        for msg in context.messenger.messages {\n            if let Some(msg) = msg.as_any().downcast_ref::<ButtonNotifyMessage>()\n                && msg.trigger_start()\n            {\n                match msg.sender.key() {\n                    \"yes\" => {\n                        context.messenger.write(\n                            notify.to_owned(),\n                            ConfirmNotifyMessage {\n                                sender: context.id.to_owned(),\n                                confirmed: true,\n                            },\n                        );\n                    }\n                    \"no\" => {\n                        context.messenger.write(\n                            notify.to_owned(),\n                            ConfirmNotifyMessage {\n                                sender: context.id.to_owned(),\n                                confirmed: false,\n                            },\n                        );\n                    }\n                    _ => {}\n                }\n            }\n        }\n    });\n}\n\n#[pre_hooks(use_confirm_box)]\npub fn confirm_box(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext { id, key, props, .. } = context;\n    let ConfirmBoxProps { text, .. } = props.read_cloned_or_default();\n\n    make_widget!(modal_paper)\n        .key(key)\n        .named_slot(\n            \"content\",\n            make_widget!(vertical_paper)\n                .key(\"list\")\n                .with_props(ContentBoxItemLayout {\n                    anchors: 0.5.into(),\n                    margin: Rect {\n                        left: -200.0,\n                        right: -200.0,\n                        top: -100.0,\n                        bottom: -100.0,\n                    },\n                    ..Default::default()\n                })\n                .with_props(VerticalBoxProps {\n                    separation: 20.0,\n                    ..Default::default()\n                })\n                .listed_slot(\n                    make_widget!(wrap_box)\n                        .key(\"text-wrap\")\n                        .with_props(WrapBoxProps {\n                            margin: 16.0.into(),\n                            ..Default::default()\n                        })\n                        .named_slot(\n                            \"content\",\n                            make_widget!(text_paper)\n                                .key(\"text\")\n                                .with_props(TextPaperProps {\n                                    text,\n                                    height: TextBoxSizeValue::Exact(24.0),\n                                    variant: \"title\".to_owned(),\n                                    ..Default::default()\n                                }),\n                        ),\n                )\n                .listed_slot(\n                    make_widget!(horizontal_box)\n                        .key(\"buttons\")\n                        .listed_slot(\n                            make_widget!(wrap_box)\n                                .key(\"yes-wrap\")\n                                .with_props(WrapBoxProps {\n                                    margin: 16.0.into(),\n                                    ..Default::default()\n                                })\n                                .named_slot(\n                                    \"content\",\n                                    make_widget!(text_button_paper)\n                                        .key(\"yes\")\n                                        .with_props(TextPaperProps {\n                                            text: \"YES\".to_owned(),\n                                            height: TextBoxSizeValue::Exact(24.0),\n                                            variant: \"button\".to_owned(),\n                                            ..Default::default()\n                                        })\n                                        .with_props(WrapBoxProps {\n                                            margin: 16.0.into(),\n                                            ..Default::default()\n                                        })\n                                        .with_props(NavItemActive)\n                                        .with_props(ButtonNotifyProps(id.to_owned().into())),\n                                ),\n                        )\n                        .listed_slot(\n                            make_widget!(wrap_box)\n                                .key(\"no-wrap\")\n                                .with_props(WrapBoxProps {\n                                    margin: 16.0.into(),\n                                    ..Default::default()\n                                })\n                                .named_slot(\n                                    \"content\",\n                                    make_widget!(text_button_paper)\n                                        .key(\"no\")\n                                        .with_props(TextPaperProps {\n                                            text: \"NO\".to_owned(),\n                                            height: TextBoxSizeValue::Exact(24.0),\n                                            variant: \"button\".to_owned(),\n                                            ..Default::default()\n                                        })\n                                        .with_props(WrapBoxProps {\n                                            margin: 16.0.into(),\n                                            ..Default::default()\n                                        })\n                                        .with_props(NavItemActive)\n                                        .with_props(ButtonNotifyProps(id.to_owned().into())),\n                                ),\n                        ),\n                ),\n        )\n        .into()\n}\n"
  },
  {
    "path": "demos/todo-app/src/ui/components/mod.rs",
    "content": "pub mod app;\npub mod app_bar;\npub mod confirm_box;\npub mod tasks_list;\n"
  },
  {
    "path": "demos/todo-app/src/ui/components/tasks_list.rs",
    "content": "use crate::{\n    model::{AppState, TaskProps},\n    ui::components::confirm_box::{ConfirmBoxProps, ConfirmNotifyMessage, confirm_box},\n};\nuse raui::{\n    core::{\n        Prefab, PropsData, make_widget, pre_hooks,\n        props::PropsData,\n        widget::{\n            component::{\n                containers::{\n                    hidden_box::{HiddenBoxProps, hidden_box},\n                    horizontal_box::HorizontalBoxProps,\n                    vertical_box::{VerticalBoxProps, vertical_box},\n                },\n                interactive::{\n                    button::{ButtonNotifyMessage, ButtonNotifyProps},\n                    navigation::{NavContainerActive, NavItemActive},\n                    scroll_view::ScrollViewRange,\n                },\n            },\n            context::WidgetContext,\n            node::WidgetNode,\n            unit::{\n                content::ContentBoxItemLayout, flex::FlexBoxItemLayout, text::TextBoxSizeValue,\n            },\n        },\n    },\n    material::{\n        component::{\n            containers::{\n                horizontal_paper::horizontal_paper,\n                paper::PaperContentLayoutProps,\n                scroll_paper::{scroll_paper, scroll_paper_side_scrollbars},\n            },\n            icon_paper::{IconImage, IconPaperProps},\n            interactive::{\n                icon_button_paper::icon_button_paper, switch_button_paper::switch_button_paper,\n            },\n            switch_paper::SwitchPaperProps,\n            text_paper::{TextPaperProps, text_paper},\n        },\n        theme::{ThemeColor, ThemeVariant, ThemedWidgetProps},\n    },\n};\nuse serde::{Deserialize, Serialize};\n\n#[derive(PropsData, Debug, Default, Copy, Clone, Serialize, Deserialize)]\nstruct TaskState {\n    #[serde(default)]\n    deleting: bool,\n}\n\nfn use_task(context: &mut WidgetContext) {\n    context.life_cycle.change(|mut context| {\n        let mut app_state = context\n            .view_models\n            .view_model_mut(AppState::VIEW_MODEL)\n            .unwrap()\n            .write::<AppState>()\n            .unwrap();\n\n        for msg in context.messenger.messages {\n            if let Some(msg) = msg.as_any().downcast_ref::<ButtonNotifyMessage>() {\n                if msg.trigger_start() {\n                    match msg.sender.key() {\n                        \"checkbox\" => {\n                            if let Ok(index) = context.id.key().parse::<usize>() {\n                                app_state.toggle_task(index);\n                            }\n                        }\n                        \"delete\" => {\n                            let _ = context.state.write_with(TaskState { deleting: true });\n                        }\n                        _ => {}\n                    }\n                }\n            } else if let Some(msg) = msg.as_any().downcast_ref::<ConfirmNotifyMessage>() {\n                let _ = context.state.write_with(TaskState { deleting: false });\n                if msg.confirmed\n                    && let Ok(index) = context.id.key().parse::<usize>()\n                {\n                    app_state.delete_task(index);\n                }\n            }\n        }\n    });\n}\n\n#[pre_hooks(use_task)]\npub fn task(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        id,\n        key,\n        props,\n        state,\n        ..\n    } = context;\n    let data = props.read_cloned_or_default::<TaskProps>();\n    let TaskState { deleting } = state.read_cloned_or_default();\n\n    make_widget!(horizontal_paper)\n        .key(key)\n        .with_props(HorizontalBoxProps {\n            separation: 10.0,\n            ..Default::default()\n        })\n        .with_props(ContentBoxItemLayout {\n            margin: 10.0.into(),\n            ..Default::default()\n        })\n        .listed_slot(\n            make_widget!(switch_button_paper)\n                .key(\"checkbox\")\n                .with_props(FlexBoxItemLayout {\n                    fill: 0.0,\n                    grow: 0.0,\n                    shrink: 0.0,\n                    align: 0.5,\n                    ..Default::default()\n                })\n                .with_props(SwitchPaperProps {\n                    on: data.done,\n                    variant: \"checkbox\".to_owned(),\n                    size_level: 2,\n                })\n                .with_props(NavItemActive)\n                .with_props(ButtonNotifyProps(id.to_owned().into()))\n                .with_props(ThemedWidgetProps {\n                    color: ThemeColor::Primary,\n                    variant: ThemeVariant::ContentOnly,\n                    ..Default::default()\n                }),\n        )\n        .listed_slot(\n            make_widget!(text_paper)\n                .key(\"name\")\n                .with_props(TextPaperProps {\n                    text: data.name,\n                    height: TextBoxSizeValue::Exact(24.0),\n                    variant: \"title\".to_owned(),\n                    ..Default::default()\n                })\n                .with_props(FlexBoxItemLayout {\n                    align: 0.5,\n                    ..Default::default()\n                }),\n        )\n        .listed_slot(\n            make_widget!(icon_button_paper)\n                .key(\"delete\")\n                .with_props(FlexBoxItemLayout {\n                    fill: 0.0,\n                    grow: 0.0,\n                    shrink: 0.0,\n                    align: 0.5,\n                    ..Default::default()\n                })\n                .with_props(IconPaperProps {\n                    image: IconImage {\n                        id: \"resources/icons/delete.png\".to_owned(),\n                        ..Default::default()\n                    },\n                    size_level: 2,\n                    ..Default::default()\n                })\n                .with_props(NavItemActive)\n                .with_props(ButtonNotifyProps(id.to_owned().into()))\n                .with_props(ThemedWidgetProps {\n                    color: ThemeColor::Primary,\n                    variant: ThemeVariant::ContentOnly,\n                    ..Default::default()\n                }),\n        )\n        .listed_slot(\n            make_widget!(hidden_box)\n                .with_props(HiddenBoxProps(!deleting))\n                .named_slot(\n                    \"content\",\n                    make_widget!(confirm_box)\n                        .key(\"confirm\")\n                        .with_props(ConfirmBoxProps {\n                            text: \"Do you want to remove task?\".to_owned(),\n                            notify: id.to_owned().into(),\n                        }),\n                ),\n        )\n        .into()\n}\n\nfn use_tasks_list(context: &mut WidgetContext) {\n    context.life_cycle.mount(|mut context| {\n        context\n            .view_models\n            .bindings(AppState::VIEW_MODEL, AppState::TASKS)\n            .unwrap()\n            .bind(context.id.to_owned());\n    });\n}\n\n#[pre_hooks(use_tasks_list)]\npub fn tasks_list(mut context: WidgetContext) -> WidgetNode {\n    let WidgetContext {\n        key, view_models, ..\n    } = context;\n    let app_state = view_models\n        .view_model(AppState::VIEW_MODEL)\n        .unwrap()\n        .read::<AppState>()\n        .unwrap();\n    let mut tasks = app_state\n        .tasks()\n        .enumerate()\n        .map(|(index, item)| {\n            make_widget!(task)\n                .key(index)\n                .with_props(item.to_owned())\n                .with_props(FlexBoxItemLayout {\n                    grow: 0.0,\n                    shrink: 0.0,\n                    ..Default::default()\n                })\n        })\n        .collect::<Vec<_>>();\n    tasks.reverse();\n\n    make_widget!(scroll_paper)\n        .key(key)\n        .with_props(NavContainerActive)\n        .with_props(NavItemActive)\n        .with_props(ScrollViewRange::default())\n        .with_props(PaperContentLayoutProps(ContentBoxItemLayout {\n            margin: 10.0.into(),\n            ..Default::default()\n        }))\n        .named_slot(\n            \"content\",\n            make_widget!(vertical_box)\n                .key(\"list\")\n                .with_props(VerticalBoxProps {\n                    separation: 30.0,\n                    ..Default::default()\n                })\n                .listed_slots(tasks),\n        )\n        .named_slot(\n            \"scrollbars\",\n            make_widget!(scroll_paper_side_scrollbars).key(\"scrollbars\"),\n        )\n        .into()\n}\n"
  },
  {
    "path": "demos/todo-app/src/ui/mod.rs",
    "content": "pub mod components;\n"
  },
  {
    "path": "justfile",
    "content": "# List the just recipe list\nlist:\n    just --list\n\n# Bake the README.md from the template\nreadme:\n    cargo readme -r ./crates/_ > README.md\n\nformat:\n    cargo fmt --all\n\nbuild:\n    cargo build --all\n    cargo build --examples\n\nclippy:\n    cargo clippy --all\n\ntest:\n    cargo test --all --features all\n    cargo test --all --examples --features all\n\nexample NAME=\"setup\":\n    cargo run --example {{NAME}}\n\ndemo NAME=\"todo-app\":\n    cd ./demos/{{NAME}} && cargo run\n\nguide NAME:\n    cd ./site/rust/guide_{{NAME}} && cargo run\n\n# Mandatory checks to run before pushing changes to repository\nchecks:\n    just format\n    just build\n    just clippy\n    just test\n    just readme\n\n# Print the documentation coverage for a crate in the workspace\ndoc-coverage crate=\"raui-core\":\n    cargo +nightly rustdoc -p {{crate}} -- -Z unstable-options --show-coverage\n\nclean:\n  find . -name target -type d -exec rm -r {} +\n  just remove-lockfiles\n\nremove-lockfiles:\n  find . -name Cargo.lock -type f -exec rm {} +\n\nlist-outdated:\n  cargo outdated -R -w\n\n# Run the Rust doctests in the website docs\nwebsite-doc-tests:\n    cargo build --features all -p raui --target-dir target/doctests\n    @set -e; \\\n    for file in $(find site/content/ -name '*.md'); do \\\n        echo \"Testing: $file\"; \\\n        rustdoc \\\n            --edition 2018 \\\n            --extern raui \\\n            --crate-name docs-test \\\n            $file \\\n            --test \\\n            -L target/doctests/debug/deps; \\\n    done\n\nwebsite-live-dev:\n    cd site && zola serve\n\nupdate:\n    cargo update --manifest-path ./crates/derive/Cargo.toml --aggressive\n    cargo update --manifest-path ./crates/core/Cargo.toml --aggressive\n    cargo update --manifest-path ./crates/material/Cargo.toml --aggressive\n    cargo update --manifest-path ./crates/retained/Cargo.toml --aggressive\n    cargo update --manifest-path ./crates/immediate/Cargo.toml --aggressive\n    cargo update --manifest-path ./crates/immediate-widgets/Cargo.toml --aggressive\n    cargo update --manifest-path ./crates/json-renderer/Cargo.toml --aggressive\n    cargo update --manifest-path ./crates/tesselate-renderer/Cargo.toml --aggressive\n    cargo update --manifest-path ./crates/app/Cargo.toml --aggressive\n    cargo update --manifest-path ./crates/_/Cargo.toml --aggressive\n\npublish:\n    cargo publish --no-verify --manifest-path ./crates/derive/Cargo.toml\n    sleep 1\n    cargo publish --no-verify --manifest-path ./crates/core/Cargo.toml\n    sleep 1\n    cargo publish --no-verify --manifest-path ./crates/material/Cargo.toml\n    sleep 1\n    cargo publish --no-verify --manifest-path ./crates/retained/Cargo.toml\n    sleep 1\n    cargo publish --no-verify --manifest-path ./crates/immediate/Cargo.toml\n    sleep 1\n    cargo publish --no-verify --manifest-path ./crates/immediate-widgets/Cargo.toml\n    sleep 1\n    cargo publish --no-verify --manifest-path ./crates/json-renderer/Cargo.toml\n    sleep 1\n    cargo publish --no-verify --manifest-path ./crates/tesselate-renderer/Cargo.toml\n    sleep 1\n    cargo publish --no-verify --manifest-path ./crates/app/Cargo.toml\n    sleep 1\n    cargo publish --no-verify --manifest-path ./crates/_/Cargo.toml\n"
  },
  {
    "path": "site/.gitignore",
    "content": "public"
  },
  {
    "path": "site/.markdownlint.yml",
    "content": "# Disable unwanted lints\nMD031: false # Spaces around codeblocks\nMD013: false # 80 char line length limit"
  },
  {
    "path": "site/config.toml",
    "content": "# The URL the site will be built for\nbase_url = \"https://RAUI-labs.github.io/raui\"\ntitle = \"RAUI\"\ndescription = \"Renderer Agnostic User Interface written in Rust\"\ntheme = \"adidoks\"\n\n# The default language; used in feeds and search index\n# Note: the search index doesn't support Chinese/Japanese/Korean Languages\ndefault_language = \"en\"\n\n# Whether to automatically compile all Sass files in the sass directory\ncompile_sass = true\n\n# Whether to generate a feed file for the site\n# generate_feed = true\n\n# When set to \"true\", the generated HTML files are minified.\nminify_html = false\n\n# The taxonomies to be rendered for the site and their configuration.\ntaxonomies = [\n  {name = \"authors\"}, # Basic definition: no feed or pagination\n]\n\n# Whether to build a search index to be used later on by a JavaScript library\n# When set to \"true\", a search index is built from the pages and section\n# content for `default_language`.\nbuild_search_index = true\n\n[search]\n# Whether to include the title of the page/section in the index\ninclude_title = true\n# Whether to include the description of the page/section in the index\ninclude_description = true\n# Whether to include the rendered content of the page/section in the index\ninclude_content = true\n\n[markdown]\n# Whether to do syntax highlighting.\n# Theme can be customised by setting the `highlight_theme`\n# variable to a theme supported by Zola\nhighlight_code = true\n\n[extra]\n# Put all your custom variables here\nauthor = \"PsichiX\"\ngithub = \"https://github.com/PsichiX\"\ntwitter = \"https://twitter.com/psichix\"\nemail = \"psichix@gmail.com\"\n\n# If running on netlify.app site, set to true\nis_netlify = true\n\n# Set HTML file language\nlanguage_code = \"en-US\"\n\n# Set HTML theme color\ntheme_color = \"#fff\"\n\n# More about site's title\ntitle_separator = \"|\"  # set as |, -, _, etc\ntitle_addition = \"Renderer Agnostic User Interface\"\n\n\n# Set date format in blog publish metadata\ntimeformat = \"%B %e, %Y\" # e.g. June 14, 2021\ntimezone = \"America/New_York\"\n\n# Edit page on reposity or not\nedit_page = true\ndocs_repo = \"https://github.com/RAUI-labs/raui\"\nrepo_branch = \"next\"\n\n## Math settings\n# options: true, false. Enable math support globally,\n# default: false. You can always enable math on a per page.\nmath = false\nlibrary = \"katex\"  # options: \"katex\", \"mathjax\". default is \"katex\".\n\n## Open Graph + Twitter Cards\n[extra.open]\nenable = false\n# this image will be used as fallback if a page has no image of its own\nimage = \"doks.png\"\ntwitter_site = \"aaranxu\"\ntwitter_creator = \"aaranxu\"\nfacebook_author = \"ichunyun\"\nfacebook_publisher = \"ichunyun\"\nog_locale = \"en_US\"\n\n## JSON-LD\n[extra.schema]\ntype = \"Organization\"\nlogo = \"logo-doks.png\"\ntwitter = \"\"\nlinked_in = \"\"\ngithub = \"https://github.com/RAUI-labs\"\nsection = \"blog\" # see config.extra.main~url\n## Sitelinks Search Box\nsite_links_search_box = false\n\n\n# Menu items\n[[extra.menu.main]]\nname = \"Docs\"\nsection = \"docs\"\nurl = \"docs/about/introduction/\"\nweight = 10\n\n[[extra.menu.main]]\nname = \"Blog\"\nsection = \"blog\"\nurl = \"/blog/\"\nweight = 20\n\n# [[extra.menu.main]]\n# name = \"Examples\"\n# section = \"examples\"\n# url = \"/examples/\"\n# weight = 30\n\n# [[extra.menu.social]]\n# name = \"Twitter\"\n# pre = \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"20\\\" height=\\\"20\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-width=\\\"2\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" class=\\\"feather feather-twitter\\\"><path d=\\\"M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.70 10.70 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z\\\"></path></svg>\"\n# url = \"https://twitter.com/aaranxu\"\n# weight = 10\n\n[[extra.menu.social]]\nname = \"GitHub\"\npre = \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"20\\\" height=\\\"20\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-width=\\\"2\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" class=\\\"feather feather-github\\\"><path d=\\\"M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22\\\"></path></svg>\"\nurl = \"https://github.com/RAUI-labs/raui\"\npost = \"v0.1.0\"\nweight = 20\n\n# Footer contents\n[extra.footer]\ninfo = \"Powered by <a href=\\\"https://www.getzola.org/\\\">Zola</a> and <a href=\\\"https://github.com/aaranxu/adidoks\\\">AdiDoks</a>\"\n\n# [[extra.footer.nav]]\n# name = \"Privacy\"\n# url = \"/privacy-policy/\"\n# weight = 10\n\n# [[extra.footer.nav]]\n# name = \"Code of Conduct\"\n# url = \"/docs/contributing/code-of-conduct/\"\n# weight = 20\n\n# The homepage contents\n[extra.home]\ntitle = \"RAUI — Renderer Agnostic UI\"\nlead = \"<b>RAUI</b> is a renderer agnostic UI, written in Rust, that is heavely inspired by <b>React</b> declarative UI composition and <b>Unreal Engine Slate</b> widget components system\"\nurl = \"/docs/about/introduction/\"\nurl_button = \"Get started\"\nrepo_version = \"Hosted on GitHub.\"\nrepo_license = \"Open-source MIT License.\"\nrepo_url = \"https://github.com/RAUI-labs/raui\"\n\n[[extra.home.list]]\ntitle = \"Use With Any Renderer! 🎮\"\ncontent = \"Easily integrate with your favorite Rust game engine or toolkit.\"\n\n[[extra.home.list]]\ntitle = \"Built-in Tesselation Renderer 📐\"\ncontent = \"RAUI features a built-in tesselator renderer that allows easily hooking into any renderer that can render triangles!\"\n\n[[extra.home.list]]\ntitle = \"Or Do it Yourself! 👨‍🏭\"\ncontent = \"If triangles aren't your thing, you can implement custom rendering of RAUI's <code>WidgetNode</code> <i>however you want</i>.\"\n\n[[extra.home.list]]\ntitle = \"Flexible Design 📈\"\ncontent = \"\"\"Easily create your own widget components simply by defining functions\"\"\"\n\n[[extra.home.list]]\ntitle = \"Existing Integrations 🏗\"\ncontent = \"\"\"RAUI is being used in <a target=\"_blank\" href=\"https://github.com/PsichiX/Oxygengine\"> Oxygengine</a> and <a target=\"_blank\" href=\"https://github.com/katharostech/bevy_retro\">Bevy Retro</a>, but slowly new integrations will be made.\"\"\"\n"
  },
  {
    "path": "site/content/authors/_index.md",
    "content": "+++\ntitle = \"Authors\"\ndescription = \"The writers of our blog posts.\"\ndraft = false\n\n# If you add a new author page in this section, please add a new item,\n# and the format is as follows:\n#\n# \"author-name-in-url\" = \"the-full-path-of-the-author-page\"\n#\n# Note: We use quoted keys here.\n[extra.author_pages]\n\"zicklag\" = \"authors/zicklag.md\"\n+++\n\nThe authors of the blog articles.\n"
  },
  {
    "path": "site/content/authors/psichix.md",
    "content": "+++\ntitle = \"PsichiX\"\ndescription = \"Creator of RAUI\"\ndate = 2021-04-01T08:50:45+00:00\nupdated = 2021-04-01T08:50:45+00:00\ndraft = false\n+++\n\nThe creator of RAUI.\n\n[@PsichiX](https://github.com/PsichiX)\n"
  },
  {
    "path": "site/content/authors/zicklag.md",
    "content": "+++\ntitle = \"Zicklag\"\ndescription = \"Contributor to RAUI\"\ndate = 2021-04-01T08:50:45+00:00\nupdated = 2021-04-01T08:50:45+00:00\ndraft = false\n+++\n\nA contributor to RAUI that works on making games [@katharostech].\n\n[@zicklag](https://github.com/zicklag)\n\n[@katharostech]: https://katharostech.com\n"
  },
  {
    "path": "site/content/blog/_index.md",
    "content": "+++\ntitle = \"Blog\"\ndescription = \"Blog\"\nsort_by = \"date\"\npaginate_by = 5\ntemplate = \"blog/section.html\"\n+++\n"
  },
  {
    "path": "site/content/blog/new-documentation-site.md",
    "content": "+++\ntitle = \"New RAUI Documentation Site! 🎉\"\ndescription = \"RAUI has a new documentation site!\"\ndate = 2021-05-11T15:07:00+00:00\nupdated = 2021-05-11T15:07:00+00:00\ndraft = false\ntemplate = \"blog/page.html\"\n\n[taxonomies]\nauthors = [\"zicklag\"]\n\n[extra]\nlead = \"We've just finished making RAUI's new documentation site and adding a ton of extra API documentation. We hope this will help make it easier to get started with RAUI bring some more polish to the developer experience.\"\n+++\n\nWith this new blog section we will also be able to make development announcements to help keep the community in the loop about what's going on with the project.\n\nWe've made a lot of progress but there's still tons of work to do. If you find bugs, or have feature requests, don't hesitate to open a [GitHub issue][issue]! And if you have questions or comments feel free to open a [discussion].\n\n[issue]: https://github.com/RAUI-labs/raui/issues\n[discussion]: https://github.com/RAUI-labs/raui/discussions\n"
  },
  {
    "path": "site/content/docs/_index.md",
    "content": "+++\ntitle = \"Docs\"\ndescription = \"Documentation\"\nweight = 1\ntemplate = \"docs/section.html\"\n+++\n"
  },
  {
    "path": "site/content/docs/about/_index.md",
    "content": "+++\ntitle = \"About\"\ntemplate = \"docs/section.html\"\nweight = 0\ndraft = false\n+++\n"
  },
  {
    "path": "site/content/docs/about/introduction.md",
    "content": "+++\ntitle = \"Introduction\"\nweight = 10\nsort_by = \"weight\"\ntemplate = \"docs/page.html\"\n\n[extra]\nlead = \"<b>RAUI</b> is a renderer agnostic UI, written in Rust, that is heavely inspired by <b>React</b> declarative UI composition and <b>Unreal Engine Slate</b> widget components system.\"\ntoc = true\ntop = false\n+++\n\n## About\n\nRAUI is designed to be flexible and easy-to-use, allowing you to quickly build your own UI components by combining existing components into more elaborate structures. The main idea behind the RAUI architecture is to treat the UI as data that gets transformed into rendering data for your renderer of choice.\n\n## API Docs\n\nFor a more in-depth explanation of the RAUI architecture see the [API documentation][api]. The API docs have explanations of each piece of RAUI and how the everything is put together.\n\n[api]: https://docs.rs/raui\n\n## Guide\n\nFor a more guide-level explanation, continue on! We're going to walk you through creating your first RAUI app.\n"
  },
  {
    "path": "site/content/docs/getting-started/01-setting-up.md",
    "content": "+++\ntitle = \"Setting Up\"\ndescription = \"Learn how to get a window setup so RAUI can render to it.\"\ndraft = false\nweight = 1\ntemplate = \"docs/page.html\"\nslug = \"setting-up\"\n\n[extra]\nlead = \"First we're going to get a window setup so RAUI can render to it.\"\ntoc = true\ntop = false\n+++\n\n## Creating the Project\n\nLet's create a new Rust project and add our dependencies.\n\nCreate a new cargo project:\n\n```bash\ncargo new --bin my_project\n```\n\nThen add the following dependencies to the `Cargo.toml`:\n\n```toml\n[dependencies]\n# The RAUI mother-crate\nraui = { version = \"*\", features = [\"app\"] }\n```\n\n## Initializing The Window\n\nNext we need to setup our UI window. Using the [`raui-app`] crate this is super easy!\n\nIn most cases you will probably want to integrate RAUI with a game engine or other renderer, and in that case you would not use [`raui-app`], you would use an integration crate like [`raui-tesselation-renderer`]. For now, though, we want to get right into RAUI without having to worry about integrations.\n\n[`raui-app`]: https://docs.rs/raui-app\n[`raui-tesselation-renderer`]: https://docs.rs/raui-tesselation-renderer\n\nGo ahead and add the following to your `main.rs` file:\n\n{{ rust_code_snippet(path=\"rust/guide_01/src/main.rs\") }}\n\nWe don't add any widgets yet, we'll get to that in the next step. At this point you should be able to `cargo run` and have a blank window pop up!\n\nOK, not that cool. We're not here for a blank window, so let's go put some GUI on the screen!\n\n> **Note:** You can find the full code for this chapter [here](https://github.com/RAUI-labs/raui/tree/master/site/rust/guide_01)"
  },
  {
    "path": "site/content/docs/getting-started/02-your-first-widget/index.md",
    "content": "+++\ntitle = \"Your First Widget\"\ndescription = \"Learn how to create your first widget\"\ndraft = false\nweight = 2\ntemplate = \"docs/page.html\"\nslug = \"your-first-widget\"\n\n[extra]\nlead = \"Now we get to create our first RAUI widget!\"\ntoc = true\ntop = false\n+++\n\n## What is a Widget?\n\nBefore we create a widget, let's talk about what a widget actually _is_. To be precise, there are a few different types of widgets and the type widget that we will be creating is a **_widget component_**. Widget components are made out of normal Rust functions that have a specific signature. The job of widget components is to look at it's properties and child widgets ( if it has any ), to do any processing that it needs to, and to output a new widget tree that will rendered in place of the component.\n\nBecause components can return whole new widget trees, they can combine the functionality of multiple _other_ components together. This allows you to build components into larger structures like LEGO® bricks, where a complex UI can be made out of many simple pieces put together in different combinations. This high modularity is the true power of RAUI, your imagination is the only limit!\n\n## Making a Widget\n\nWithout further ado, let's make a widget!\n\nUpdate your `main.rs` file to look like this:\n\n{{ rust_code_snippet(path=\"rust/guide_02/src/main.rs\")}}\n\nThere's plenty of comments in the above example, but there's a lot going on here so lets break it down piece by piece.\n\n### Setting the Widget Tree\n\nThe first thing we changed was to add our app component to the widget tree in our `DeclarativeApp` instance.\n\n{{ rust_code_snippet(path=\"rust/guide_02/src/main.rs\", start=3, end=5)}}\n\nEvery RAUI app has a root widget tree that holds the whole structure of application in it. It is essentially the \"main\" of the UI. In this case we set our widget to a single `app` component. This is the `app` component that we define in the function below.\n\nHere we use the strategy of keeping our root widget tree very simple and putting all of our real logic in the `app` component.\n\n### The `app` Component\n\nNow we get to the definition of our `app` component:\n\n{{ rust_code_snippet(path=\"rust/guide_02/src/main.rs\", start=9, end=9)}}\n\nAs we mentioned before, components are just normal Rust functions with a specific signature. Components are required to take a [`WidgetContext`] as an argument and they must return a [`WidgetNode`].\n\n[`WidgetContext`]: https://docs.rs/raui/latest/raui/core/widget/context/struct.WidgetContext.html\n[`WidgetNode`]: https://docs.rs/raui/latest/raui/core/widget/node/enum.WidgetNode.html\n\n#### [`WidgetContext`]\n\nThe widget context is the way that a widget can access it's own state, properties, children, and other information that may be important to the function of the widget. It also allows the widget to respond to events or send messages to other widgets. We will learn more about the [`WidgetContext`] later.\n\n#### [`WidgetNode`]\n\nThat brings us to what a **widget _node_** is. As we mentioned above, there are a few different kinds of _widgets_. Widget components are one of them and widget nodes are another. The easiest way to think of a [`WidgetNode`] is that it is a tree of other widgets. [`WidgetNode`]s are most commonly created with the [`make_widget!`] macro and filled with builder pattern of [`WidgetComponent`].\n\n> **Note:** There is a third kind of widget is called a [`WidgetUnit`], but you don't usually need to think about those, since they are the final representation of processed widgets tree.\n\nWe can see the [`make_widget!`] macro with builder pattern in action in our example:\n\n{{ rust_code_snippet(path=\"rust/guide_02/src/main.rs\", start=16, end=33)}}\n\nWe use that pattern to create a simple tree with a single [`text_box`] component in it and we apply our `text_box_props` to it to configure how the text box renders the text inside.\n\n[`text_box`]: https://docs.rs/raui/latest/raui/core/widget/component/text_box/fn.text_box.html\n\n[`make_widget!`]: https://docs.rs/raui/latest/raui/core/macro.make_widget.html\n[`WidgetUnit`]: https://docs.rs/raui/latest/raui/core/widget/unit/enum.WidgetUnit.html\n[`WidgetComponent`]: https://docs.rs/raui/latest/raui/core/widget/component/enum.WidgetComponent.html\n\n#### Properties\n\nThat brings us to the concept of **_properties_**. Properties are data, made up of Rust structs, that can be applied to components additively to customize their behavior.\n\nIn this case we created [`TextBoxProps`] data that we used to configure the [`text_box`] component.\n\n{{ rust_code_snippet(path=\"rust/guide_02/src/main.rs\", start=17, end=32)}}\n\nWe use the properties to configure the font, content, and color of our text.\n\nWe are not limited to using just one struct for our property data. We could add any number of different properties structs to our component, allowing us to configure how the component responds to layout, for instance. We will see how to do more of that later.\n\n[`TextBoxProps`]: https://docs.rs/raui/0.34.0/raui/core/widget/component/text_box/struct.TextBoxProps.html\n\n> **Note:** You can download the [Verdana] font or use your own font to follow along. We chose to place it in a `resources/` folder adjacent to our `Cargo.toml` file in our Rust project, but you can place it wherever you like as long as you update the path to the font in the [`TextBoxProps`].\n\n[Verdana]: https://github.com/PsichiX/raui/raw/master/site/rust/guide_02/resources/verdana.ttf\n\n## Summary\n\nNow that we've explained it all, go try it out! When you `cargo run` you should get a window displaying your \"Hello World!\".\n\n![hello world screenshot](hello_world.png)\n\n> **Note:** You can find the whole code for this chapter [here](https://github.com/RAUI-labs/raui/tree/master/site/rust/guide_02).\n\n## 🚧 Under Construction 👷\n\nThere should be more to this guide but it isn't written yet! If you've gotten this far, congratulations and thank you for reading! Come back later and see whether or not more of the guide has been written.\n\nIf you need help or have questions, feel free to open up a [discussion] on GitHub. 👋\n\n[discussion]: https://github.com/RAUI-labs/raui/discussions\n"
  },
  {
    "path": "site/content/docs/getting-started/03-containers/index.md",
    "content": "+++\ntitle = \"Containers\"\ndescription = \"Learn how to use container widgets.\"\ndraft = false\nweight = 3\ntemplate = \"docs/page.html\"\nslug = \"containers\"\n\n[extra]\nlead = \"Now we're going to try out some built-in container widgets that will allow us to organize our content and build layouts.\"\ntoc = true\ntop = false\n+++\n\nRAUI comes with a number of built-in container components that can be used to help group and lay out other components. You can find the list of core container types in the [API documentation][containers].\n\n[containers]: https://docs.rs/raui/latest/raui/core/widget/component/containers/index.html\n\n## Content Box\n\nThe first container we'll look at is the [`content_box`]. Content boxes are simple containers that can hold multiple items, where each item's position is **not** effected by any of the other items in the box.\n\nA couple useful features of [`content_box`]s are that they can have a [transform][`contentboxprops::transform`] applied to them to scale, position, rotate, etc. the box and everything in it, and items inside them can specify [layout attributes][`contentboxitemlayout`] such as margin, alignment, and anchor.\n\n> **Note:** A content box's transform is purely a _visual_ effect and has no effect on the layout of the box or the regions used to detect clicks, hovers, etc. for the box or it's children. It is used primarily in animations and visual effects, not for general purpose positioning.\n\nIn our demo app we've been working on, let's put our text in a content box so that we can set a small margin around our text.\n\n[`contentboxitemlayout`]: https://docs.rs/raui/latest/raui/core/widget/unit/content/struct.ContentBoxItemLayout.html\n[`contentboxprops::transform`]: https://docs.rs/raui/latest/raui/core/widget/component/containers/content_box/struct.ContentBoxProps.html#structfield.transform\n[`content_box`]: https://docs.rs/raui/latest/raui/core/widget/component/containers/content_box/fn.content_box.html\n\n{% rustdoc_test() %}\n\n```rust\n# use raui::import_all::*;\n/// Our app widget from earlier\npub fn app(_ctx: WidgetContext) -> WidgetNode {\n    // Create our text box properties\n    let text_box_props = Props::new(TextBoxProps {\n        text: \"Hello World!\".into(),\n        color: Color {\n            r: 0.0,\n            g: 0.0,\n            b: 0.0,\n            a: 1.0,\n        },\n        font: TextBoxFont {\n           // We specify the path to our font\n            name: \"resources/verdana.ttf\".to_owned(),\n            size: 60.0,\n        },\n        // Use the defaults for the rest of the text box settings\n        ..Default::default()\n    })\n    // Here we use the `with` function on `Props` _add_ to another property to our\n    // text box props list. In this case we add the `ContentBoxItemLayout` struct to\n    // influence its layout when it is a child of a `content_box`.\n    .with(ContentBoxItemLayout {\n        // Specify a margin of 10 on every side\n        margin: 10.0.into(),\n        // Use the default value for the rest of the layout options\n        ..Default::default()\n    });\n\n    make_widget!(content_box)\n        .listed_slot(make_widget!(text_box).merge_props(text_box_props))\n        .into()\n}\n```\n\n{% end %}\n\nNow if we run our app we should have a small margin between the text and the sides of the window.\n\nBut what if we want to add another item to our box, like maybe an image? Let's see what happens if we place an image in the content box with our text:\n\n{% rustdoc_test() %}\n\n```rust\n# use raui::import_all::*;\npub fn app(_ctx: WidgetContext) -> WidgetNode {\n    // Create our text box properties\n    // ...\n#    let text_box_props = Props::new(TextBoxProps {\n#        text: \"Hello World!\".into(),\n#        color: Color {\n#            r: 0.0,\n#            g: 0.0,\n#            b: 0.0,\n#            a: 1.0,\n#        },\n#        font: TextBoxFont {\n#            // We specify the path to our font\n#            name: \"resources/verdana.ttf\".to_owned(),\n#            size: 60.0,\n#        },\n#        // Use the defaults for the rest of the text box settings\n#        ..Default::default()\n#    })\n#    .with(ContentBoxItemLayout {\n#        // Specify a margin of 10 on every side\n#        margin: 10.0.into(),\n#        // Use the default value for the rest of the layout options\n#        ..Default::default()\n#    });\n\n    // Create the props for our image\n    let image_box_props = Props::new(ImageBoxProps {\n        // The material defines what image or color to use for the box\n        material: ImageBoxMaterial::Image(ImageBoxImage {\n            // The path to our image\n            id: \"resources/cats.jpg\".to_owned(),\n            ..Default::default()\n        }),\n        // Have the image fill it's container\n        width: ImageBoxSizeValue::Fill,\n        height: ImageBoxSizeValue::Fill,\n        ..Default::default()\n    });\n\n    make_widget!(content_box)\n        .listed_slot(make_widget!(image_box).merge_props(image_box_props))\n        .listed_slot(make_widget!(text_box).merge_props(text_box_props))\n        .into()\n}\n```\n\n{% end %}\n\n![text and image](text-and-image.png)\n\n> **Note:** You can download the demo cat image [here](https://github.com/PsichiX/raui/raw/master/site/rust/guide_03/resources/cats.jpg).\n\nNotice that the content box didn't make sure there was any \"room\" for the text or the image to sit side-by-side with each-other, it just stacked them right on top of each-other. Also\n\nIf we wanted to have them line up without overlapping we would use a [`flex_box`].\n\n[`flex_box`]: https://docs.rs/raui/latest/raui/core/widget/component/containers/flex_box/fn.flex_box.html\n\n## Flex Box\n\nFlex boxes in RAUI are similar to [CSS flexboxes][css_flexboxes]. Flex boxes lay things out in a row or a column depending on whether or not their _direction_. For convenience, RAUI has [`vertical_box`] and [`horizontal_box`] components that are simply [`flex_box`] component with their direction set to vertical or horizontal by default.\n\n> **Note:** For a more in-depth explanation of how layout works in RAUI see [Layout in Depth](../../layout/layout-in-depth).\n\n[css_flexboxes]: https://www.w3schools.com/css/css3_flexbox.asp\n[`horizontal_box`]: https://docs.rs/raui/latest/raui/core/widget/component/containers/horizontal_box/fn.horizontal_box.html\n[`vertical_box`]: https://docs.rs/raui/latest/raui/core/widget/component/containers/vertical_box/fn.vertical_box.html\n\nLet's go ahead and use a [`vertical_box`] to put our text above our cat photo:\n\n{% rustdoc_test() %}\n\n```rust\n# use raui::import_all::*;\npub fn app(_ctx: WidgetContext) -> WidgetNode {\n    // Create our text box properties\n    let text_box_props = Props::new(TextBoxProps {\n        text: \"Hello World!\".into(),\n        color: Color {\n            r: 0.0,\n            g: 0.0,\n            b: 0.0,\n            a: 1.0,\n        },\n        font: TextBoxFont {\n            // We specify the path to our font\n            name: \"resources/verdana.ttf\".to_owned(),\n            size: 60.0,\n        },\n        // Use the defaults for the rest of the text box settings\n        ..Default::default()\n    })\n    // Notice that we now use a `FlexBoxItemLayout` instead of a `ContentBoxItemLayout`\n    // because we are putting it in a flex box instead of a content box\n    .with(FlexBoxItemLayout {\n        margin: Rect {\n            // Let's just set a left margin this time\n            left: 30.,\n            ..Default::default()\n        },\n        ..Default::default()\n    });\n\n    // Create the props for our image\n    let image_box_props = Props::new(ImageBoxProps {\n        material: ImageBoxMaterial::Image(ImageBoxImage {\n            id: \"resources/cats.jpg\".to_owned(),\n            ..Default::default()\n        }),\n        ..Default::default()\n    });\n\n    // Use a vertical_box instead of a content_box\n    make_widget!(vertical_box)\n        // Now because the text and image won't overlap, let's put\n        // the text above the image\n        .listed_slot(make_widget!(text_box).merge_props(text_box_props))\n        .listed_slot(make_widget!(image_box).merge_props(image_box_props))\n        .into()\n}\n```\n\n{% end %}\n\n![flex box layout](flex-box.png)\n\nThere we go! Notice that with the flex box layout, the text box and the image box are both taking up an equal amount of space vertically. That's how flex boxes work by default. Each item in the box will get an equal amount of space along the flex box's direction. This is configurable using the other settings in the [`FlexBoxItemLayout`] struct.\n\n[`flexboxitemlayout`]: htps://docs.rs/raui/latest/raui/core/widget/unit/flex/struct.FlexBoxItemLayout.html\n\nNext we'll learn how to make our UI react to user input.\n\n> **Note:** You can access the full source code for this chapter [here](https://github.com/RAUI-labs/raui/tree/master/site/rust/guide_03).\n\n## 🚧 Under Construction 👷\n\nThere should be more to this guide but it isn't written yet! If you've gotten this far, congratulations and thank you for reading! Come back later and see whether or not more of the guide has been written.\n\nIf you need help or have questions, feel free to open up a [discussion] on GitHub. 👋\n\n[discussion]: https://github.com/PsichiX/raui/discussions\n"
  },
  {
    "path": "site/content/docs/getting-started/_index.md",
    "content": "+++\ntitle = \"Getting Started\"\ndescription = \"Quick start and guides for getting RAUI up and ready.\"\ntemplate = \"docs/section.html\"\nsort_by = \"weight\"\nweight = 10\ndraft = false\n+++\n"
  },
  {
    "path": "site/content/docs/layout/01-layout-in-depth.md",
    "content": "+++\ntitle = \"Layout In Depth\"\ndescription = \"Detailed explanation of how layout works in RAUI by default\"\ndraft = false\nweight = 1\ntemplate = \"docs/page.html\"\nslug = \"layout-in-depth\"\n\n[extra]\nlead = \"RAUI comes out of the box with a swap-able layout engine. Here's an explanation of how the default layout engine in RAUI works.\"\ntoc = true\ntop = false\n+++\n\n> **🚧 Under Construction:** This section is coming soon!\n"
  },
  {
    "path": "site/content/docs/layout/_index.md",
    "content": "+++\ntitle = \"Layout\"\ndescription = \"Explanations of how layout works in RAUI\"\ntemplate = \"docs/section.html\"\nsort_by = \"weight\"\nweight = 20\ndraft = false\n+++\n"
  },
  {
    "path": "site/content/examples/_index.md",
    "content": "+++\ntitle = \"Examples\"\ndescription = \"Examples\"\nweight = 2\ntemplate = \"docs/section.html\"\n+++\n\nList of examples showcasing day-to-day usecases.\n"
  },
  {
    "path": "site/rust/guide_01/Cargo.toml",
    "content": "[package]\nname = \"guide_01\"\nversion = \"0.1.0\"\nauthors = [\"RAUI Contributors\"]\nedition = \"2024\"\n\npublish = false\n\n[dependencies]\n# The main RAUI crate\nraui = { version = \"0.70\", path = \"../../../crates/_\", features = [\n    \"app\",\n    \"import-all\",\n] }\n"
  },
  {
    "path": "site/rust/guide_01/src/main.rs",
    "content": "use raui::{app::app::declarative::DeclarativeApp, core::widget::node::WidgetNode};\n\nfn main() {\n    DeclarativeApp::simple(\"RAUI Guide\", WidgetNode::default());\n}\n"
  },
  {
    "path": "site/rust/guide_02/Cargo.toml",
    "content": "[package]\nname = \"guide_02\"\nversion = \"0.1.0\"\nauthors = [\"RAUI Contributors\"]\nedition = \"2024\"\n\npublish = false\n\n[dependencies]\n# The main RAUI crate\nraui = { version = \"0.70\", path = \"../../../crates/_\", features = [\n    \"app\",\n    \"import-all\",\n] }\n"
  },
  {
    "path": "site/rust/guide_02/src/main.rs",
    "content": "use raui::{\n    app::app::declarative::DeclarativeApp,\n    core::{\n        make_widget,\n        widget::{\n            component::text_box::{TextBoxProps, text_box},\n            context::WidgetContext,\n            node::WidgetNode,\n            unit::text::TextBoxFont,\n            utils::Color,\n        },\n    },\n};\n\nfn main() {\n    DeclarativeApp::simple(\"RAUI Guide\", make_widget!(app));\n}\n\n/// We create our own widget by making a function that takes a `WidgetContext`\n/// and that returns `WidgetNode`.\npub fn app(_ctx: WidgetContext) -> WidgetNode {\n    // Our _ctx variable starts with an underscore so rust doesn't complain\n    // that it is unused. We will be using the context later in the guide.\n\n    // We may do any amount of processing in the body of the function.\n    // For now we will simply be creating a text box properties struct that we\n    // will use to configure the `text_box` component.\n    make_widget!(text_box)\n        .with_props(TextBoxProps {\n            text: \"Hello world!\".to_owned(),\n            color: Color {\n                r: 0.0,\n                g: 0.0,\n                b: 0.0,\n                a: 1.0,\n            },\n            font: TextBoxFont {\n                // We specify the path to our font\n                name: \"resources/verdana.ttf\".to_owned(),\n                size: 60.0,\n            },\n            // Use the defaults for the rest of the text box settings\n            ..Default::default()\n        })\n        .into()\n}\n"
  },
  {
    "path": "site/rust/guide_03/Cargo.toml",
    "content": "[package]\nname = \"guide_03\"\nversion = \"0.1.0\"\nauthors = [\"RAUI Contributors\"]\nedition = \"2024\"\n\npublish = false\n\n[dependencies]\n# The main RAUI crate\nraui = { version = \"0.70\", path = \"../../../crates/_\", features = [\n    \"app\",\n    \"import-all\",\n] }\n"
  },
  {
    "path": "site/rust/guide_03/src/main.rs",
    "content": "use raui::{\n    app::app::declarative::DeclarativeApp,\n    core::{\n        make_widget,\n        widget::{\n            component::{\n                containers::vertical_box::vertical_box,\n                image_box::{ImageBoxProps, image_box},\n                text_box::{TextBoxProps, text_box},\n            },\n            context::WidgetContext,\n            node::WidgetNode,\n            unit::{\n                flex::FlexBoxItemLayout,\n                image::{ImageBoxAspectRatio, ImageBoxImage, ImageBoxMaterial},\n                text::{TextBoxFont, TextBoxHorizontalAlign},\n            },\n            utils::{Color, Rect},\n        },\n    },\n};\n\nfn main() {\n    DeclarativeApp::simple(\"RAUI Guide\", make_widget!(app));\n}\n\n// Our app widget from earlier\npub fn app(_ctx: WidgetContext) -> WidgetNode {\n    make_widget!(vertical_box)\n        .listed_slot(\n            make_widget!(text_box)\n                .with_props(TextBoxProps {\n                    text: \"Hello World!\".into(),\n                    color: Color {\n                        r: 0.0,\n                        g: 0.0,\n                        b: 0.0,\n                        a: 1.0,\n                    },\n                    font: TextBoxFont {\n                        // We specify the path to our font\n                        name: \"resources/verdana.ttf\".to_owned(),\n                        size: 60.0,\n                    },\n                    horizontal_align: TextBoxHorizontalAlign::Center,\n                    // Use the defaults for the rest of the text box settings\n                    ..Default::default()\n                })\n                // Notice that we now use a `FlexBoxItemLayout` instead of a `ContentBoxItemLayout`\n                // because we are putting it in a flex box instead of a content box\n                .with_props(FlexBoxItemLayout {\n                    basis: Some(80.0),\n                    grow: 0.0,\n                    shrink: 0.0,\n                    margin: Rect {\n                        // Let's just set a left margin this time\n                        left: 30.,\n                        ..Default::default()\n                    },\n                    ..Default::default()\n                }),\n        )\n        .listed_slot(make_widget!(image_box).with_props(ImageBoxProps {\n            // The material defines what image or color to use for the box\n            material: ImageBoxMaterial::Image(ImageBoxImage {\n                // The path to our image\n                id: \"resources/cats.jpg\".to_owned(),\n                ..Default::default()\n            }),\n            // this allows image content to not stretch to fill its container.\n            content_keep_aspect_ratio: Some(ImageBoxAspectRatio {\n                horizontal_alignment: 0.5,\n                vertical_alignment: 0.5,\n                ..Default::default()\n            }),\n            ..Default::default()\n        }))\n        .into()\n}\n"
  },
  {
    "path": "site/static/.nojekyll",
    "content": ""
  },
  {
    "path": "site/templates/shortcodes/code_snippet.md",
    "content": "{% set lines = load_data(path=path, format=\"plain\") | split(pat=\"\\n\") -%}\n{% set lines_count = lines | length() -%}\n{% if not start -%}\n    {% set start = 0 -%}\n{% else %}\n    {% set start = start - 1 %}\n{% endif -%}\n{% if not end -%}\n    {% set end = lines_count -%}\n{% endif -%}\n\n{% if not lang -%}\n    {% set lang = \"rust\" -%}\n{% endif -%}\n\n```{{lang}}\n{{ lines | slice(start=start, end=end) | join(sep=\"\n\") | trim_end}}\n```\n"
  },
  {
    "path": "site/templates/shortcodes/include_markdown.md",
    "content": "{{ load_data(path=path) }}"
  },
  {
    "path": "site/templates/shortcodes/rust_code_snippet.md",
    "content": "{% set lines = load_data(path=path, format=\"plain\") | split(pat=\"\\n\") -%}\n{% set lines_count = lines | length() -%}\n{% if not start -%}\n    {% set start = 0 -%}\n{% else %}\n    {% set start = start - 1 %}\n{% endif -%}\n{% if not end -%}\n    {% set end = lines_count -%}\n{% endif -%}\n\n```rust\n{{ lines | slice(start=start, end=end) | join(sep=\"\n\") | trim_end}}\n```\n"
  },
  {
    "path": "site/templates/shortcodes/rustdoc_test.md",
    "content": "{% set lines = body | split(pat=\"\\n\") -%}\n{% for line in lines -%}\n{% if not line is starting_with(\"# \") -%}\n{{line | replace(from=\"```rust,ignore\", to=\"```rust\")}}\n{% endif -%}\n{% endfor -%}\n"
  },
  {
    "path": "site/templates/shortcodes/toml_code_snippet.md",
    "content": "{% set lines = load_data(path=path, format=\"plain\") | split(pat=\"\\n\") -%}\n{% set lines_count = lines | length() -%}\n{% if not start -%}\n    {% set start = 0 -%}\n{% else %}\n    {% set start = start - 1 %}\n{% endif -%}\n{% if not end -%}\n    {% set end = lines_count -%}\n{% endif -%}\n\n```toml\n{{ lines | slice(start=start, end=end) | join(sep=\"\n\") | trim_end}}\n```\n"
  }
]