diff options
222 files changed, 2626 insertions, 5728 deletions
diff --git a/.github/ISSUE_TEMPLATE/BUG-REPORT.yml b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml new file mode 100644 index 00000000..9d0eb2ed --- /dev/null +++ b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml @@ -0,0 +1,82 @@ +name: I have a problem with the library +description: File a bug report. +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + - type: checkboxes + attributes: + label: Is there an existing issue for this? + description: | + Please, search [the existing issues] and see if an issue already exists for the bug you encountered. + + [the existing issues]: https://github.com/iced-rs/iced/issues + options: + - label: I have searched the existing issues. + required: true + - type: checkboxes + attributes: + label: Is this issue related to iced? + description: | + If your application is crashing during startup or you are observing graphical glitches, there is a chance it may be caused by incompatible hardware or outdated graphics drivers. + + Before filing an issue... + + - If you are using `wgpu`, you need an environment that supports Vulkan, Metal, or DirectX 12. Please, make sure you can run [the `wgpu` examples]. + - If you are using `glow`, you need support for OpenGL 2.1+. Please, make sure you can run [the `glow` examples]. + + If you have any issues running any of the examples, make sure your graphics drivers are up-to-date. If the issues persist, please report them to the authors of the libraries directly! + + [the `wgpu` examples]: https://github.com/gfx-rs/wgpu/tree/master/wgpu/examples + [the `glow` examples]: https://github.com/grovesNL/glow/tree/main/examples + options: + - label: My hardware is compatible and my graphics drivers are up-to-date. + required: true + - type: textarea + attributes: + label: What happened? + id: what-happened + description: | + What problem are you having? Please, also provide the steps to reproduce it. + + If the issue happens with a particular program, please share an [SSCCE]. + + [SSCCE]: http://sscce.org/ + validations: + required: true + - type: textarea + attributes: + label: What is the expected behavior? + id: what-expected + description: What were you expecting to happen? + validations: + required: true + - type: dropdown + id: version + attributes: + label: Version + description: What version of iced are you using? + options: + - master + - 0.3.0 + validations: + required: true + - type: dropdown + id: os + attributes: + label: Operative System + description: Which operative system are you using? + options: + - Windows + - macOS + - Linux + validations: + required: true + - type: textarea + id: logs + attributes: + label: Do you have any log output? + description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + render: shell diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..00e4748d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: I have a question + url: https://github.com/iced-rs/iced/discussions/new?category=q-a + about: Open a discussion with a Q&A format. + - name: I want to start a discussion + url: https://github.com/iced-rs/iced/discussions/new + about: Open a new discussion if you have any suggestions, ideas, feature requests, or simply want to show off something you've made. + - name: I want to chat with other users of the library + url: https://discord.com/invite/3xZJ65GAhd + about: Join the Discord Server and get involved with the community! diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 2ffa10a8..30bb3004 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -11,6 +11,5 @@ jobs: - name: Manually update `nix` crates # See https://github.com/nix-rust/nix/issues/1627 run: | cargo update --package nix:0.20.0 --precise 0.20.2 - cargo update --package nix:0.22.0 --precise 0.22.2 - name: Audit dependencies run: cargo audit diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 25909870..beec168b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -82,3 +82,29 @@ jobs: with: name: todos-x86_64-apple-darwin path: target/release/todos + + todos_raspberry: + runs-on: ubuntu-latest + steps: + - uses: hecrj/setup-rust-action@v1 + - uses: actions/checkout@master + - name: Install cross + run: cargo install cross + - name: Enable Link Time Optimizations + run: | + echo "[profile.release]" >> Cargo.toml + echo "lto = true" >> Cargo.toml + - name: Build todos binary for Raspberry Pi 3/4 (64 bits) + run: cross build --verbose --release --package todos --target aarch64-unknown-linux-gnu + - name: Archive todos binary + uses: actions/upload-artifact@v1 + with: + name: todos-aarch64-unknown-linux-gnu + path: target/aarch64-unknown-linux-gnu/release/todos + - name: Build todos binary for Raspberry Pi 2/3/4 (32 bits) + run: cross build --verbose --release --package todos --target armv7-unknown-linux-gnueabihf + - name: Archive todos binary + uses: actions/upload-artifact@v1 + with: + name: todos-armv7-unknown-linux-gnueabihf + path: target/armv7-unknown-linux-gnueabihf/release/todos diff --git a/.github/workflows/document.yml b/.github/workflows/document.yml index 849c0778..3a8326b6 100644 --- a/.github/workflows/document.yml +++ b/.github/workflows/document.yml @@ -17,7 +17,6 @@ jobs: -p iced_core \ -p iced_native \ -p iced_lazy \ - -p iced_web \ -p iced_graphics \ -p iced_wgpu \ -p iced_glow \ diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0450f13d..433afadc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,3 +37,5 @@ jobs: run: cargo build --package tour --target wasm32-unknown-unknown - name: Check compilation of `todos` example run: cargo build --package todos --target wasm32-unknown-unknown + - name: Check compilation of `integration_wgpu` example + run: cargo build --package integration_wgpu --target wasm32-unknown-unknown @@ -3,3 +3,4 @@ pkg/ **/*.rs.bk Cargo.lock .cargo/ +dist/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e3be755..a476631d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -108,7 +108,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [qr_code]: https://github.com/iced-rs/iced/pull/622 [#193]: https://github.com/iced-rs/iced/pull/193 [`glutin`]: https://github.com/rust-windowing/glutin -[`wgpu`]: https://github.com/gfx-rs/wgpu-rs +[`wgpu`]: https://github.com/gfx-rs/wgpu [`glow`]: https://github.com/grovesNL/glow [the `qrcode` crate]: https://docs.rs/qrcode/0.12.0/qrcode/ [integration with existing applications]: https://github.com/iced-rs/iced/pull/183 @@ -207,7 +207,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [`wasm-bindgen-futures`]: https://github.com/rustwasm/wasm-bindgen/tree/master/crates/futures [`resvg`]: https://github.com/RazrFalcon/resvg [`raqote`]: https://github.com/jrmuizel/raqote -[`iced_wgpu`]: https://github.com/iced-rs/iced/tree/master/wgpu +[`iced_wgpu`]: wgpu/ ## [0.1.0-beta] - 2019-11-25 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 39e4c776..cf6655f5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ Thank you for considering contributing to Iced! Feel free to read [the ecosystem overview] and [the roadmap] to get an idea of the current state of the library. -The main advice for new contributors is to share your ideas with the community. Introduce yourself over our [Zulip server] or [start a discussion in an issue](https://github.com/hecrj/iced/issues) explaining what you have in mind (do not be afraid of duplicated issues!). If you want to talk directly to me (@hecrj), you can also find me on Discord (`lone_scientist#9554`). +The main advice for new contributors is to share your ideas with the community. Introduce yourself over our [Discord server] or [start a discussion in an issue](https://github.com/hecrj/iced/issues) explaining what you have in mind (do not be afraid of duplicated issues!). If you want to talk directly to me (@hecrj), you can also find me on Discord (`lone_scientist#9554`). This is a very important step. It helps to coordinate work, get on the same page, and start building trust. Please, do not skip it! Remember that [Code is the Easy Part] and also [The Hard Parts of Open Source]! @@ -27,7 +27,7 @@ Besides directly writing code, there are many other different ways you can contr [the ecosystem overview]: ECOSYSTEM.md [the roadmap]: ROADMAP.md -[Zulip server]: https://iced.zulipchat.com/ +[Discord server]: https://discord.gg/3xZJ65GAhd [Code is the Easy Part]: https://youtu.be/DSjbTC-hvqQ?t=1138 [The Hard Parts of Open Source]: https://www.youtube.com/watch?v=o_4EX4dPppA [`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph @@ -2,7 +2,7 @@ name = "iced" version = "0.3.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" description = "A cross-platform GUI library inspired by Elm" license = "MIT" repository = "https://github.com/iced-rs/iced" @@ -13,7 +13,7 @@ categories = ["gui"] resolver = "2" [features] -default = ["wgpu", "default_system_font"] +default = ["wgpu"] # Enables the `iced_wgpu` renderer wgpu = ["iced_wgpu"] # Enables the `Image` widget @@ -58,7 +58,6 @@ members = [ "lazy", "native", "style", - "web", "wgpu", "winit", "examples/bezier_tool", @@ -94,16 +93,16 @@ members = [ [dependencies] iced_core = { version = "0.4", path = "core" } iced_futures = { version = "0.3", path = "futures" } +iced_winit = { version = "0.3", path = "winit" } +iced_glutin = { version = "0.2", path = "glutin", optional = true } +iced_glow = { version = "0.2", path = "glow", optional = true } thiserror = "1.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -iced_winit = { version = "0.3", path = "winit" } -iced_glutin = { version = "0.2", path = "glutin", optional = true } iced_wgpu = { version = "0.4", path = "wgpu", optional = true } -iced_glow = { version = "0.2", path = "glow", optional = true} [target.'cfg(target_arch = "wasm32")'.dependencies] -iced_web = { version = "0.4", path = "web" } +iced_wgpu = { version = "0.4", path = "wgpu", features = ["webgl"], optional = true } [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] diff --git a/Cross.toml b/Cross.toml new file mode 100644 index 00000000..a3675817 --- /dev/null +++ b/Cross.toml @@ -0,0 +1,7 @@ +[target.aarch64-unknown-linux-gnu] +image = "ghcr.io/iced-rs/aarch64:latest" +xargo = false + +[target.armv7-unknown-linux-gnueabihf] +image = "ghcr.io/iced-rs/armv7:latest" +xargo = false diff --git a/ECOSYSTEM.md b/ECOSYSTEM.md index 82303130..86581e4a 100644 --- a/ECOSYSTEM.md +++ b/ECOSYSTEM.md @@ -45,7 +45,7 @@ The widgets of a _graphical_ user interface produce some primitives that eventua Currently, there are two different official renderers: - [`iced_wgpu`] is powered by [`wgpu`] and supports Vulkan, DirectX 12, and Metal. -- [`iced_glow`] is powered by [`glow`] and supports OpenGL 3.3+. +- [`iced_glow`] is powered by [`glow`] and supports OpenGL 2.1+ and OpenGL ES 2.0+. Additionally, the [`iced_graphics`] subcrate contains a bunch of backend-agnostic types that can be leveraged to build renderers. Both of the renderers rely on the graphical foundations provided by this crate. @@ -78,7 +78,7 @@ Finally, [`iced`] unifies everything into a simple abstraction to create cross-p [`iced_futures`]: futures [`iced_style`]: style [`iced_native`]: native -[`iced_web`]: web +[`iced_web`]: https://github.com/iced-rs/iced_web [`iced_graphics`]: graphics [`iced_wgpu`]: wgpu [`iced_glow`]: glow @@ -87,7 +87,7 @@ Finally, [`iced`] unifies everything into a simple abstraction to create cross-p [`iced`]: .. [`futures`]: https://github.com/rust-lang/futures-rs [`glow`]: https://github.com/grovesNL/glow -[`wgpu`]: https://github.com/gfx-rs/wgpu-rs +[`wgpu`]: https://github.com/gfx-rs/wgpu [`winit`]: https://github.com/rust-windowing/winit [`glutin`]: https://github.com/rust-windowing/glutin [`dodrio`]: https://github.com/fitzgen/dodrio @@ -34,27 +34,32 @@ Inspired by [Elm]. * First-class support for async actions (use futures!) * [Modular ecosystem] split into reusable parts: * A [renderer-agnostic native runtime] enabling integration with existing systems - * A [built-in renderer] supporting Vulkan, Metal, DX11, and DX12 + * Two [built-in renderers] leveraging [`wgpu`] and [`glow`] + * [`iced_wgpu`] supporting Vulkan, Metal and DX12 + * [`iced_glow`] supporting OpenGL 2.1+ and OpenGL ES 2.0+ * A [windowing shell] * A [web runtime] leveraging the DOM __iced is currently experimental software.__ [Take a look at the roadmap], [check out the issues], and [feel free to contribute!] -[Cross-platform support]: https://github.com/hecrj/iced/blob/master/docs/images/todos_desktop.jpg?raw=true +[Cross-platform support]: https://raw.githubusercontent.com/iced-rs/iced/master/docs/images/todos_desktop.jpg [the Web]: https://iced.rs/ [text inputs]: https://gfycat.com/alertcalmcrow-rust-gui [scrollables]: https://gfycat.com/perkybaggybaboon-rust-gui [Debug overlay with performance metrics]: https://gfycat.com/incredibledarlingbee -[Modular ecosystem]: https://github.com/hecrj/iced/blob/master/ECOSYSTEM.md -[renderer-agnostic native runtime]: https://github.com/hecrj/iced/tree/master/native -[`wgpu`]: https://github.com/gfx-rs/wgpu-rs -[built-in renderer]: https://github.com/hecrj/iced/tree/master/wgpu -[windowing shell]: https://github.com/hecrj/iced/tree/master/winit +[Modular ecosystem]: ECOSYSTEM.md +[renderer-agnostic native runtime]: native/ +[`wgpu`]: https://github.com/gfx-rs/wgpu +[`glow`]: https://github.com/grovesNL/glow +[`iced_wgpu`]: wgpu/ +[`iced_glow`]: glow/ +[built-in renderers]: ECOSYSTEM.md#Renderers +[windowing shell]: winit/ [`dodrio`]: https://github.com/fitzgen/dodrio -[web runtime]: https://github.com/hecrj/iced/tree/master/web -[Take a look at the roadmap]: https://github.com/hecrj/iced/blob/master/ROADMAP.md -[check out the issues]: https://github.com/hecrj/iced/issues +[web runtime]: https://github.com/iced-rs/iced_web +[Take a look at the roadmap]: ROADMAP.md +[check out the issues]: https://github.com/iced-rs/iced/issues [feel free to contribute!]: #contributing--feedback ## Installation @@ -67,7 +72,7 @@ iced = "0.3" __iced moves fast and the `master` branch can contain breaking changes!__ If you want to learn about a specific release, check out [the release list]. -[the release list]: https://github.com/hecrj/iced/releases +[the release list]: https://github.com/iced-rs/iced/releases ## Overview Inspired by [The Elm Architecture], iced expects you to split user interfaces @@ -183,17 +188,43 @@ Since then, the focus has shifted towards providing a batteries-included, end-user-oriented GUI library, while keeping [the ecosystem] modular: <p align="center"> - <a href="https://github.com/hecrj/iced/blob/master/ECOSYSTEM.md"> + <a href="ECOSYSTEM.md"> <img alt="iced ecosystem" src="docs/graphs/ecosystem.png" width="80%"> </a> </p> [this pull request]: https://github.com/hecrj/coffee/pull/35 -[The first alpha version]: https://github.com/hecrj/iced/tree/0.1.0-alpha +[The first alpha version]: https://github.com/iced-rs/iced/tree/0.1.0-alpha [a renderer-agnostic GUI library]: https://www.reddit.com/r/rust/comments/czzjnv/iced_a_rendereragnostic_gui_library_focused_on/ -[tour example]: https://github.com/hecrj/iced/blob/master/examples/README.md#tour +[tour example]: examples/README.md#tour [`ggez`]: https://github.com/ggez/ggez -[the ecosystem]: https://github.com/hecrj/iced/blob/master/ECOSYSTEM.md +[the ecosystem]: ECOSYSTEM.md + +## Common problems + +1. `Error: GraphicsAdapterNotFound` + + This occurs when the selected [built-in renderer] is not able to create a context. + + Often this will occur while using [`iced_wgpu`] as the renderer without + supported hardware (needs Vulkan, Metal or DX12). In this case, you could try using the + [`iced_glow`] renderer: + + First, check if it works with + ```console + $ cargo run --features "iced/glow iced/glow_canvas" --package game_of_life + ``` + + and then use it in your project with + ```toml + iced = { version = "0.3", default-features = false, features = ["glow"] } + ``` + + **NOTE:** Chances are you have hardware that supports at least OpenGL 2.1 or OpenGL ES 2.0, + but if you don't, right now there's no software fallback, so it means your hardware + doesn't support Iced. + +[built-in renderer]: https://github.com/hecrj/iced/blob/master/ECOSYSTEM.md#Renderers ## Contributing / Feedback Contributions are greatly appreciated! If you want to contribute, please @@ -20,15 +20,15 @@ Once a step is completed, it is collapsed and added to this list: * [x] Canvas for 2D graphics ([#193]) * [x] Basic overlay support ([#444]) -[#24]: https://github.com/hecrj/iced/issues/24 -[#25]: https://github.com/hecrj/iced/issues/25 -[#26]: https://github.com/hecrj/iced/issues/26 -[#28]: https://github.com/hecrj/iced/issues/28 -[#52]: https://github.com/hecrj/iced/pull/52 -[#122]: https://github.com/hecrj/iced/pull/122 -[#146]: https://github.com/hecrj/iced/pull/146 -[#193]: https://github.com/hecrj/iced/pull/193 -[#444]: https://github.com/hecrj/iced/pull/444 +[#24]: https://github.com/iced-rs/iced/issues/24 +[#25]: https://github.com/iced-rs/iced/issues/25 +[#26]: https://github.com/iced-rs/iced/issues/26 +[#28]: https://github.com/iced-rs/iced/issues/28 +[#52]: https://github.com/iced-rs/iced/pull/52 +[#122]: https://github.com/iced-rs/iced/pull/122 +[#146]: https://github.com/iced-rs/iced/pull/146 +[#193]: https://github.com/iced-rs/iced/pull/193 +[#444]: https://github.com/iced-rs/iced/pull/444 ### Multi-window support ([#27]) Open and control multiple windows at runtime. @@ -37,7 +37,7 @@ I think this could be achieved by implementing an additional trait in `iced_wini This approach should also allow us to perform custom optimizations for this particular use case. -[#27]: https://github.com/hecrj/iced/issues/27 +[#27]: https://github.com/iced-rs/iced/issues/27 ### Animations ([#31]) Allow widgets to request a redraw at a specific time. @@ -46,7 +46,7 @@ This is a necessary feature to render loading spinners, a blinking text cursor, [`winit`] allows flexible control of its event loop. We may be able to use [`ControlFlow::WaitUntil`](https://docs.rs/winit/0.20.0-alpha3/winit/event_loop/enum.ControlFlow.html#variant.WaitUntil) for this purpose. -[#31]: https://github.com/hecrj/iced/issues/31 +[#31]: https://github.com/iced-rs/iced/issues/31 ### Canvas widget for 3D graphics ([#32]) A widget to draw freely in 3D. It could be used to draw charts, implement a Paint clone, a CAD application, etc. @@ -55,7 +55,7 @@ As a first approach, we could expose the underlying renderer directly here, and In the long run, we could expose a renderer-agnostic abstraction to perform the drawing. -[#32]: https://github.com/hecrj/iced/issues/32 +[#32]: https://github.com/iced-rs/iced/issues/32 ### Text shaping and font fallback ([#33]) [`wgpu_glyph`] uses [`glyph_brush`], which in turn uses [`rusttype`]. While the current implementation is able to layout text quite nicely, it does not perform any [text shaping]. @@ -66,7 +66,7 @@ The only available library that does a great job at shaping is [HarfBuzz], which This feature will probably imply rewriting [`wgpu_glyph`] entirely, as caching will be more complicated and the API will probably need to ask for more data. -[#33]: https://github.com/hecrj/iced/issues/33 +[#33]: https://github.com/iced-rs/iced/issues/33 [`rusttype`]: https://github.com/redox-os/rusttype [text shaping]: https://en.wikipedia.org/wiki/Complex_text_layout [HarfBuzz]: https://github.com/harfbuzz/harfbuzz @@ -77,7 +77,7 @@ Currently, `iced_native` only supports flexbox items. For instance, it is not po We will need to enhance the layouting engine to support different strategies and improve the way we measure text to lay it out in a more flexible way. -[#34]: https://github.com/hecrj/iced/issues/34 +[#34]: https://github.com/iced-rs/iced/issues/34 ## Ideas that may be worth exploring @@ -121,6 +121,6 @@ This could be very useful to build very performant user interfaces with a lot of [Elm]: https://elm-lang.org/ [`winit`]: https://github.com/rust-windowing/winit -[`wgpu`]: https://github.com/gfx-rs/wgpu-rs +[`wgpu`]: https://github.com/gfx-rs/wgpu [`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph [`glyph_brush`]: https://github.com/alexheretic/glyph-brush diff --git a/core/Cargo.toml b/core/Cargo.toml index ca28c308..92b8c56a 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -2,7 +2,7 @@ name = "iced_core" version = "0.4.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" description = "The essential concepts of Iced" license = "MIT" repository = "https://github.com/iced-rs/iced" @@ -13,3 +13,6 @@ bitflags = "1.2" [dependencies.palette] version = "0.5" optional = true + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-timer = { version = "0.2" } diff --git a/core/README.md b/core/README.md index c16eb779..bbb7983c 100644 --- a/core/README.md +++ b/core/README.md @@ -2,7 +2,7 @@ [][documentation] [](https://crates.io/crates/iced_core) [](https://github.com/iced-rs/iced/blob/master/LICENSE) -[](https://iced.zulipchat.com) +[](https://discord.gg/3xZJ65GAhd) `iced_core` holds basic reusable types of the public API. For instance, basic data types like `Point`, `Rectangle`, `Length`, etc. diff --git a/core/src/content_fit.rs b/core/src/content_fit.rs new file mode 100644 index 00000000..6bbedc7a --- /dev/null +++ b/core/src/content_fit.rs @@ -0,0 +1,119 @@ +//! Control the fit of some content (like an image) within a space. +use crate::Size; + +/// The strategy used to fit the contents of a widget to its bounding box. +/// +/// Each variant of this enum is a strategy that can be applied for resolving +/// differences in aspect ratio and size between the image being displayed and +/// the space its being displayed in. +/// +/// For an interactive demonstration of these properties as they are implemented +/// in CSS, see [Mozilla's docs][1], or run the `tour` example +/// +/// [1]: https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit +#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)] +pub enum ContentFit { + /// Scale as big as it can be without needing to crop or hide parts. + /// + /// The image will be scaled (preserving aspect ratio) so that it just fits + /// within the window. This won't distort the image or crop/hide any edges, + /// but if the image doesn't fit perfectly, there may be whitespace on the + /// top/bottom or left/right. + /// + /// This is a great fit for when you need to display an image without losing + /// any part of it, particularly when the image itself is the focus of the + /// screen. + Contain, + + /// Scale the image to cover all of the bounding box, cropping if needed. + /// + /// This doesn't distort the image, and it ensures that the widget's area is + /// completely covered, but it might crop off a bit of the edges of the + /// widget, particularly when there is a big difference between the aspect + /// ratio of the widget and the aspect ratio of the image. + /// + /// This is best for when you're using an image as a background, or to fill + /// space, and any details of the image around the edge aren't too + /// important. + Cover, + + /// Distort the image so the widget is 100% covered without cropping. + /// + /// This stretches the image to fit the widget, without any whitespace or + /// cropping. However, because of the stretch, the image may look distorted + /// or elongated, particularly when there's a mismatch of aspect ratios. + Fill, + + /// Don't resize or scale the image at all. + /// + /// This will not apply any transformations to the provided image, but also + /// means that unless you do the math yourself, the widget's area will not + /// be completely covered, or the image might be cropped. + /// + /// This is best for when you've sized the image yourself. + None, + + /// Scale the image down if it's too big for the space, but never scale it + /// up. + /// + /// This works much like [`Contain`](Self::Contain), except that if the + /// image would have been scaled up, it keeps its original resolution to + /// avoid the bluring that accompanies upscaling images. + ScaleDown, +} + +impl ContentFit { + /// Attempt to apply the given fit for a content size within some bounds. + /// + /// The returned value is the recommended scaled size of the content. + pub fn fit(&self, content: Size, bounds: Size) -> Size { + let content_ar = content.width / content.height; + let bounds_ar = bounds.width / bounds.height; + + match self { + Self::Contain => { + if bounds_ar > content_ar { + Size { + width: content.width * bounds.height / content.height, + ..bounds + } + } else { + Size { + height: content.height * bounds.width / content.width, + ..bounds + } + } + } + Self::Cover => { + if bounds_ar < content_ar { + Size { + width: content.width * bounds.height / content.height, + ..bounds + } + } else { + Size { + height: content.height * bounds.width / content.width, + ..bounds + } + } + } + Self::Fill => bounds, + Self::None => content, + Self::ScaleDown => { + if bounds_ar > content_ar && bounds.height < content.height { + Size { + width: content.width * bounds.height / content.height, + ..bounds + } + } else if bounds.width < content.width { + Size { + height: content.height * bounds.width / content.width, + ..bounds + } + } else { + content + } + } + } + } +} diff --git a/core/src/lib.rs b/core/src/lib.rs index b76c6d20..3eb9f659 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -17,9 +17,11 @@ pub mod alignment; pub mod keyboard; pub mod mouse; +pub mod time; mod background; mod color; +mod content_fit; mod font; mod length; mod padding; @@ -31,6 +33,7 @@ mod vector; pub use alignment::Alignment; pub use background::Background; pub use color::Color; +pub use content_fit::ContentFit; pub use font::Font; pub use length::Length; pub use padding::Padding; diff --git a/core/src/time.rs b/core/src/time.rs new file mode 100644 index 00000000..f496d1a4 --- /dev/null +++ b/core/src/time.rs @@ -0,0 +1,9 @@ +//! Keep track of time, both in native and web platforms! + +#[cfg(target_arch = "wasm32")] +pub use wasm_timer::Instant; + +#[cfg(not(target_arch = "wasm32"))] +pub use std::time::Instant; + +pub use std::time::Duration; diff --git a/docs/graphs/ecosystem.png b/docs/graphs/ecosystem.png Binary files differindex 8b418c52..25929287 100644 --- a/docs/graphs/ecosystem.png +++ b/docs/graphs/ecosystem.png diff --git a/docs/graphs/foundations.png b/docs/graphs/foundations.png Binary files differindex cc043c99..280cd9fa 100644 --- a/docs/graphs/foundations.png +++ b/docs/graphs/foundations.png diff --git a/docs/graphs/iced.png b/docs/graphs/iced.png Binary files differindex bf777e53..a42f4f81 100644 --- a/docs/graphs/iced.png +++ b/docs/graphs/iced.png diff --git a/docs/graphs/native.png b/docs/graphs/native.png Binary files differindex 6a8759e0..61e01c36 100644 --- a/docs/graphs/native.png +++ b/docs/graphs/native.png diff --git a/docs/images/checkbox.png b/docs/images/checkbox.png Binary files differindex 9bb68edd..4df12f81 100644 --- a/docs/images/checkbox.png +++ b/docs/images/checkbox.png diff --git a/docs/images/radio.png b/docs/images/radio.png Binary files differindex 359b7694..a5929c6f 100644 --- a/docs/images/radio.png +++ b/docs/images/radio.png diff --git a/docs/images/text.png b/docs/images/text.png Binary files differindex 6d520fa5..11343cd0 100644 --- a/docs/images/text.png +++ b/docs/images/text.png diff --git a/docs/images/text_input.png b/docs/images/text_input.png Binary files differindex 14c8326c..9f13f22f 100644 --- a/docs/images/text_input.png +++ b/docs/images/text_input.png diff --git a/docs/images/todos_desktop.jpg b/docs/images/todos_desktop.jpg Binary files differindex a604fd62..7294b3fd 100644 --- a/docs/images/todos_desktop.jpg +++ b/docs/images/todos_desktop.jpg diff --git a/docs/logo.svg b/docs/logo.svg index 387db449..ff4eb3a7 100644 --- a/docs/logo.svg +++ b/docs/logo.svg @@ -1,14 +1 @@ -<svg width="68" height="68" viewBox="0 0 68 68" fill="none" xmlns="http://www.w3.org/2000/svg"> -<rect width="68" height="68" rx="18" fill="url(#paint0_linear)"/> -<path fill-rule="evenodd" clip-rule="evenodd" d="M52.2952 16.986L43.0273 26.4088L41.0081 24.4228L50.2761 15L52.2952 16.986ZM43.6175 17.0578L29.7156 31.192L27.6964 29.206L41.5983 15.0718L43.6175 17.0578ZM23.3109 24.9347C25.065 23.1615 26.8188 21.3887 30.4157 17.7317L32.4348 19.7177C28.8353 23.3774 27.0795 25.1523 25.3241 26.9267C23.8744 28.3921 22.425 29.8572 19.9355 32.3857L30.4685 35.1388L39.0223 26.442L41.0414 28.428L32.4876 37.1247L35.4218 47.6353L49.3019 33.7718L51.3033 35.7756L35.1269 51.9327L20.0283 47.7728L15.6165 32.7371L16.2 32.1438C19.7995 28.4842 21.5555 26.7091 23.3109 24.9347ZM32.6721 48.3186L29.7581 37.8804L19.2731 35.1398L22.3017 45.4614L32.6721 48.3186ZM48.3953 29.7021L50.7414 27.3561L48.7387 25.3535L46.3844 27.7078L36.4462 37.812L38.4654 39.7979L48.3953 29.7021Z" fill="url(#paint1_linear)"/> -<defs> -<linearGradient id="paint0_linear" x1="34" y1="0" x2="34" y2="68" gradientUnits="userSpaceOnUse"> -<stop stop-color="#00A3FF"/> -<stop offset="1" stop-color="#3300FF"/> -</linearGradient> -<linearGradient id="paint1_linear" x1="20.5" y1="47.4656" x2="60" y2="6.96558" gradientUnits="userSpaceOnUse"> -<stop stop-color="white"/> -<stop offset="1" stop-color="white" stop-opacity="0.65"/> -</linearGradient> -</defs> -</svg>
\ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" width="68" height="68" fill="none" viewBox="0 0 68 68"><rect width="68" height="68" fill="url(#paint0_linear)" rx="18"/><path fill="url(#paint1_linear)" fill-rule="evenodd" d="M52.2952 16.986L43.0273 26.4088L41.0081 24.4228L50.2761 15L52.2952 16.986ZM43.6175 17.0578L29.7156 31.192L27.6964 29.206L41.5983 15.0718L43.6175 17.0578ZM23.3109 24.9347C25.065 23.1615 26.8188 21.3887 30.4157 17.7317L32.4348 19.7177C28.8353 23.3774 27.0795 25.1523 25.3241 26.9267C23.8744 28.3921 22.425 29.8572 19.9355 32.3857L30.4685 35.1388L39.0223 26.442L41.0414 28.428L32.4876 37.1247L35.4218 47.6353L49.3019 33.7718L51.3033 35.7756L35.1269 51.9327L20.0283 47.7728L15.6165 32.7371L16.2 32.1438C19.7995 28.4842 21.5555 26.7091 23.3109 24.9347ZM32.6721 48.3186L29.7581 37.8804L19.2731 35.1398L22.3017 45.4614L32.6721 48.3186ZM48.3953 29.7021L50.7414 27.3561L48.7387 25.3535L46.3844 27.7078L36.4462 37.812L38.4654 39.7979L48.3953 29.7021Z" clip-rule="evenodd"/><defs><linearGradient id="paint0_linear" x1="34" x2="34" y1="0" y2="68" gradientUnits="userSpaceOnUse"><stop stop-color="#00A3FF"/><stop offset="1" stop-color="#30F"/></linearGradient><linearGradient id="paint1_linear" x1="20.5" x2="60" y1="47.466" y2="6.966" gradientUnits="userSpaceOnUse"><stop stop-color="#fff"/><stop offset="1" stop-color="#fff" stop-opacity=".65"/></linearGradient></defs></svg>
\ No newline at end of file diff --git a/examples/README.md b/examples/README.md index c6d2d949..137d134c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -18,9 +18,9 @@ The __[`main`](tour/src/main.rs)__ file contains all the code of the example! Al [`iced_winit`]: ../winit [`iced_native`]: ../native [`iced_wgpu`]: ../wgpu -[`iced_web`]: ../web +[`iced_web`]: https://github.com/iced-rs/iced_web [`winit`]: https://github.com/rust-windowing/winit -[`wgpu`]: https://github.com/gfx-rs/wgpu-rs +[`wgpu`]: https://github.com/gfx-rs/wgpu You can run the native version with `cargo run`: ``` @@ -29,7 +29,7 @@ cargo run --package tour The web version can be run by following [the usage instructions of `iced_web`] or by accessing [iced.rs](https://iced.rs/)! -[the usage instructions of `iced_web`]: ../web#usage +[the usage instructions of `iced_web`]: https://github.com/iced-rs/iced_web#usage ## [Todos](todos) A todos tracker inspired by [TodoMVC]. It showcases dynamic layout, text input, checkboxes, scrollables, icons, and async actions! It automatically saves your tasks in the background, even if you did not finish typing them. diff --git a/examples/bezier_tool/Cargo.toml b/examples/bezier_tool/Cargo.toml index a88975a7..890e3027 100644 --- a/examples/bezier_tool/Cargo.toml +++ b/examples/bezier_tool/Cargo.toml @@ -2,7 +2,7 @@ name = "bezier_tool" version = "0.1.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" publish = false [dependencies] diff --git a/examples/clock/Cargo.toml b/examples/clock/Cargo.toml index 29457c0d..5e869eb5 100644 --- a/examples/clock/Cargo.toml +++ b/examples/clock/Cargo.toml @@ -2,7 +2,7 @@ name = "clock" version = "0.1.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" publish = false [dependencies] diff --git a/examples/color_palette/Cargo.toml b/examples/color_palette/Cargo.toml index 00f33e20..23670b46 100644 --- a/examples/color_palette/Cargo.toml +++ b/examples/color_palette/Cargo.toml @@ -2,7 +2,7 @@ name = "color_palette" version = "0.1.0" authors = ["Clark Moody <clark@clarkmoody.com>"] -edition = "2018" +edition = "2021" publish = false [dependencies] diff --git a/examples/color_palette/README.md b/examples/color_palette/README.md index e70188f8..95a23f48 100644 --- a/examples/color_palette/README.md +++ b/examples/color_palette/README.md @@ -4,7 +4,7 @@ A color palette generator, based on a user-defined root color. <div align="center"> <a href="https://gfycat.com/dirtylonebighornsheep"> - <img src="https://github.com/hecrj/iced/raw/1a8d253611d3796b0a32b2f096bb54565a5292e0/examples/color_palette/screenshot.png"> + <img src="screenshot.png"> </a> </div> diff --git a/examples/color_palette/screenshot.png b/examples/color_palette/screenshot.png Binary files differindex aa4772e0..e8da35c4 100644 --- a/examples/color_palette/screenshot.png +++ b/examples/color_palette/screenshot.png diff --git a/examples/component/Cargo.toml b/examples/component/Cargo.toml index 5761db9f..dd435201 100644 --- a/examples/component/Cargo.toml +++ b/examples/component/Cargo.toml @@ -2,7 +2,7 @@ name = "component" version = "0.1.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" publish = false [dependencies] diff --git a/examples/counter/Cargo.toml b/examples/counter/Cargo.toml index a763cd78..e31f440f 100644 --- a/examples/counter/Cargo.toml +++ b/examples/counter/Cargo.toml @@ -2,7 +2,7 @@ name = "counter" version = "0.1.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" publish = false [dependencies] diff --git a/examples/counter/index.html b/examples/counter/index.html new file mode 100644 index 00000000..d2e368e4 --- /dev/null +++ b/examples/counter/index.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8" content="text/html; charset=utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <title>Counter - Iced</title> + <base data-trunk-public-url /> +</head> +<body> +<link data-trunk rel="rust" href="Cargo.toml" data-wasm-opt="z" data-bin="counter" /> +</body> +</html> diff --git a/examples/custom_widget/Cargo.toml b/examples/custom_widget/Cargo.toml index 86b0d2a9..067aab4a 100644 --- a/examples/custom_widget/Cargo.toml +++ b/examples/custom_widget/Cargo.toml @@ -2,7 +2,7 @@ name = "custom_widget" version = "0.1.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" publish = false [dependencies] diff --git a/examples/custom_widget/src/main.rs b/examples/custom_widget/src/main.rs index b32cb83d..28edf256 100644 --- a/examples/custom_widget/src/main.rs +++ b/examples/custom_widget/src/main.rs @@ -11,9 +11,7 @@ mod circle { // implemented by `iced_wgpu` and other renderers. use iced_native::layout::{self, Layout}; use iced_native::renderer; - use iced_native::{ - Color, Element, Hasher, Length, Point, Rectangle, Size, Widget, - }; + use iced_native::{Color, Element, Length, Point, Rectangle, Size, Widget}; pub struct Circle { radius: f32, @@ -45,12 +43,6 @@ mod circle { layout::Node::new(Size::new(self.radius * 2.0, self.radius * 2.0)) } - fn hash_layout(&self, state: &mut Hasher) { - use std::hash::Hash; - - self.radius.to_bits().hash(state); - } - fn draw( &self, renderer: &mut Renderer, diff --git a/examples/download_progress/Cargo.toml b/examples/download_progress/Cargo.toml index 5f5eed68..f38679ea 100644 --- a/examples/download_progress/Cargo.toml +++ b/examples/download_progress/Cargo.toml @@ -2,7 +2,7 @@ name = "download_progress" version = "0.1.0" authors = ["Songtronix <contact@songtronix.com>", "Folyd <lyshuhow@gmail.com>"] -edition = "2018" +edition = "2021" publish = false [dependencies] diff --git a/examples/events/Cargo.toml b/examples/events/Cargo.toml index f883075f..8ad04a36 100644 --- a/examples/events/Cargo.toml +++ b/examples/events/Cargo.toml @@ -2,7 +2,7 @@ name = "events" version = "0.1.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" publish = false [dependencies] diff --git a/examples/game_of_life/Cargo.toml b/examples/game_of_life/Cargo.toml index ffd2f19e..f0a794fb 100644 --- a/examples/game_of_life/Cargo.toml +++ b/examples/game_of_life/Cargo.toml @@ -2,7 +2,7 @@ name = "game_of_life" version = "0.1.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" publish = false [dependencies] @@ -10,3 +10,4 @@ iced = { path = "../..", features = ["canvas", "tokio", "debug"] } tokio = { version = "1.0", features = ["sync"] } itertools = "0.9" rustc-hash = "1.1" +env_logger = "0.9" diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 50112618..ab8b80e4 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -18,6 +18,8 @@ use preset::Preset; use std::time::{Duration, Instant}; pub fn main() -> iced::Result { + env_logger::builder().format_timestamp(None).init(); + GameOfLife::run(Settings { antialiasing: true, window: window::Settings { diff --git a/examples/geometry/Cargo.toml b/examples/geometry/Cargo.toml index 34eec4fb..22ede0e0 100644 --- a/examples/geometry/Cargo.toml +++ b/examples/geometry/Cargo.toml @@ -2,7 +2,7 @@ name = "geometry" version = "0.1.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" publish = false [dependencies] diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs index 6ef12013..58dfa3ad 100644 --- a/examples/geometry/src/main.rs +++ b/examples/geometry/src/main.rs @@ -14,8 +14,7 @@ mod rainbow { use iced_graphics::{Backend, Primitive}; use iced_native::{ - layout, Element, Hasher, Layout, Length, Point, Rectangle, Size, - Vector, Widget, + layout, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget, }; pub struct Rainbow; @@ -48,8 +47,6 @@ mod rainbow { layout::Node::new(Size::new(size.width, size.width)) } - fn hash_layout(&self, _state: &mut Hasher) {} - fn draw( &self, renderer: &mut Renderer<B>, diff --git a/examples/integration_opengl/Cargo.toml b/examples/integration_opengl/Cargo.toml index 0917f2ec..6dac999c 100644 --- a/examples/integration_opengl/Cargo.toml +++ b/examples/integration_opengl/Cargo.toml @@ -2,7 +2,7 @@ name = "integration_opengl" version = "0.1.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" publish = false [dependencies] @@ -10,4 +10,3 @@ iced_glutin = { path = "../../glutin" } iced_glow = { path = "../../glow" } iced_winit = { path = "../../winit" } env_logger = "0.8" -glow = "0.6" diff --git a/examples/integration_opengl/src/main.rs b/examples/integration_opengl/src/main.rs index 551aba67..1007b90f 100644 --- a/examples/integration_opengl/src/main.rs +++ b/examples/integration_opengl/src/main.rs @@ -4,16 +4,15 @@ mod scene; use controls::Controls; use scene::Scene; -use glow; use glow::*; +use glutin::dpi::PhysicalPosition; +use glutin::event::{Event, ModifiersState, WindowEvent}; +use glutin::event_loop::ControlFlow; +use iced_glow::glow; use iced_glow::{Backend, Renderer, Settings, Viewport}; +use iced_glutin::conversion; use iced_glutin::glutin; -use iced_glutin::glutin::event::{Event, WindowEvent}; -use iced_glutin::glutin::event_loop::ControlFlow; use iced_glutin::{program, Clipboard, Debug, Size}; -use iced_winit::conversion; -use iced_winit::winit; -use winit::{dpi::PhysicalPosition, event::ModifiersState}; pub fn main() { env_logger::init(); diff --git a/examples/integration_opengl/src/scene.rs b/examples/integration_opengl/src/scene.rs index a1245bf2..fc74b78a 100644 --- a/examples/integration_opengl/src/scene.rs +++ b/examples/integration_opengl/src/scene.rs @@ -1,4 +1,5 @@ use glow::*; +use iced_glow::glow; use iced_glow::Color; pub struct Scene { diff --git a/examples/integration_wgpu/.gitignore b/examples/integration_wgpu/.gitignore new file mode 100644 index 00000000..e188dc28 --- /dev/null +++ b/examples/integration_wgpu/.gitignore @@ -0,0 +1,2 @@ +*.wasm +*.js diff --git a/examples/integration_wgpu/Cargo.toml b/examples/integration_wgpu/Cargo.toml index a088dd1b..eaa1df7e 100644 --- a/examples/integration_wgpu/Cargo.toml +++ b/examples/integration_wgpu/Cargo.toml @@ -2,10 +2,21 @@ name = "integration_wgpu" version = "0.1.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" publish = false [dependencies] iced_winit = { path = "../../winit" } -iced_wgpu = { path = "../../wgpu", features = ["spirv"] } +iced_wgpu = { path = "../../wgpu", features = ["webgl"] } env_logger = "0.8" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +console_error_panic_hook = "0.1.7" +console_log = "0.2.0" +log = "0.4" +wasm-bindgen = "0.2" +web-sys = { version = "0.3", features = ["Element", "HtmlCanvasElement", "Window", "Document"] } +# This dependency a little bit quirky, it is deep in the tree and without `js` feature it +# refuses to work with `wasm32-unknown-unknown target`. Unfortunately, we need this patch +# to make it work +getrandom = { version = "0.2", features = ["js"] } diff --git a/examples/integration_wgpu/README.md b/examples/integration_wgpu/README.md index c51c2c65..faefa153 100644 --- a/examples/integration_wgpu/README.md +++ b/examples/integration_wgpu/README.md @@ -15,5 +15,22 @@ You can run it with `cargo run`: cargo run --package integration ``` +### How to run this example with WebGL backend +NOTE: Currently, WebGL backend is is still experimental, so expect bugs. + +```sh +# 0. Install prerequisites +cargo install wasm-bindgen-cli https +# 1. cd to the current folder +# 2. Compile wasm module +cargo build -p integration_wgpu --target wasm32-unknown-unknown +# 3. Invoke wasm-bindgen +wasm-bindgen ../../target/wasm32-unknown-unknown/debug/integration_wgpu.wasm --out-dir . --target web --no-typescript +# 4. run http server +http +# 5. Open 127.0.0.1:8000 in browser +``` + + [`main`]: src/main.rs [`wgpu`]: https://github.com/gfx-rs/wgpu diff --git a/examples/integration_wgpu/index.html b/examples/integration_wgpu/index.html new file mode 100644 index 00000000..461e67a4 --- /dev/null +++ b/examples/integration_wgpu/index.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html> + <head> + <meta http-equiv="Content-type" content="text/html; charset=utf-8"/> + <title>Iced - wgpu + WebGL integration</title> + </head> + <body> + <h1>integration_wgpu</h1> + <canvas id="iced_canvas"></canvas> + <script type="module"> + import init from "./integration_wgpu.js"; + init('./integration_wgpu_bg.wasm'); + </script> + <style> + body { + width: 100%; + text-align: center; + } + </style> + </body> +</html> diff --git a/examples/integration_wgpu/src/controls.rs b/examples/integration_wgpu/src/controls.rs index 4f110bd2..9bca40eb 100644 --- a/examples/integration_wgpu/src/controls.rs +++ b/examples/integration_wgpu/src/controls.rs @@ -1,23 +1,29 @@ use iced_wgpu::Renderer; use iced_winit::widget::slider::{self, Slider}; +use iced_winit::widget::text_input::{self, TextInput}; use iced_winit::widget::{Column, Row, Text}; use iced_winit::{Alignment, Color, Command, Element, Length, Program}; pub struct Controls { background_color: Color, + text: String, sliders: [slider::State; 3], + text_input: text_input::State, } #[derive(Debug, Clone)] pub enum Message { BackgroundColorChanged(Color), + TextChanged(String), } impl Controls { pub fn new() -> Controls { Controls { background_color: Color::BLACK, + text: Default::default(), sliders: Default::default(), + text_input: Default::default(), } } @@ -35,6 +41,9 @@ impl Program for Controls { Message::BackgroundColorChanged(color) => { self.background_color = color; } + Message::TextChanged(text) => { + self.text = text; + } } Command::none() @@ -42,7 +51,9 @@ impl Program for Controls { fn view(&mut self) -> Element<Message, Renderer> { let [r, g, b] = &mut self.sliders; + let t = &mut self.text_input; let background_color = self.background_color; + let text = &self.text; let sliders = Row::new() .width(Length::Units(500)) @@ -96,7 +107,13 @@ impl Program for Controls { Text::new(format!("{:?}", background_color)) .size(14) .color(Color::WHITE), - ), + ) + .push(TextInput::new( + t, + "Placeholder", + text, + move |text| Message::TextChanged(text), + )), ), ) .into() diff --git a/examples/integration_wgpu/src/main.rs b/examples/integration_wgpu/src/main.rs index 35a69a7d..045ee0d3 100644 --- a/examples/integration_wgpu/src/main.rs +++ b/examples/integration_wgpu/src/main.rs @@ -14,11 +14,39 @@ use winit::{ event_loop::{ControlFlow, EventLoop}, }; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::JsCast; +#[cfg(target_arch = "wasm32")] +use web_sys::HtmlCanvasElement; +#[cfg(target_arch = "wasm32")] +use winit::platform::web::WindowBuilderExtWebSys; + pub fn main() { + #[cfg(target_arch = "wasm32")] + let canvas_element = { + console_log::init_with_level(log::Level::Debug) + .expect("could not initialize logger"); + std::panic::set_hook(Box::new(console_error_panic_hook::hook)); + + web_sys::window() + .and_then(|win| win.document()) + .and_then(|doc| doc.get_element_by_id("iced_canvas")) + .and_then(|element| element.dyn_into::<HtmlCanvasElement>().ok()) + .expect("Canvas with id `iced_canvas` is missing") + }; + #[cfg(not(target_arch = "wasm32"))] env_logger::init(); // Initialize winit let event_loop = EventLoop::new(); + + #[cfg(target_arch = "wasm32")] + let window = winit::window::WindowBuilder::new() + .with_canvas(Some(canvas_element)) + .build(&event_loop) + .expect("Failed to build winit window"); + + #[cfg(not(target_arch = "wasm32"))] let window = winit::window::Window::new(&event_loop).unwrap(); let physical_size = window.inner_size(); @@ -31,18 +59,35 @@ pub fn main() { let mut clipboard = Clipboard::connect(&window); // Initialize wgpu - let instance = wgpu::Instance::new(wgpu::Backends::PRIMARY); + + #[cfg(target_arch = "wasm32")] + let default_backend = wgpu::Backends::GL; + #[cfg(not(target_arch = "wasm32"))] + let default_backend = wgpu::Backends::PRIMARY; + + let backend = + wgpu::util::backend_bits_from_env().unwrap_or(default_backend); + + let instance = wgpu::Instance::new(backend); let surface = unsafe { instance.create_surface(&window) }; let (format, (mut device, queue)) = futures::executor::block_on(async { - let adapter = instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::HighPerformance, - compatible_surface: Some(&surface), - force_fallback_adapter: false, - }) - .await - .expect("Request adapter"); + let adapter = wgpu::util::initialize_adapter_from_env_or_default( + &instance, + backend, + Some(&surface), + ) + .await + .expect("No suitable GPU adapters found on the system!"); + + let adapter_features = adapter.features(); + + #[cfg(target_arch = "wasm32")] + let needed_limits = wgpu::Limits::downlevel_webgl2_defaults() + .using_resolution(adapter.limits()); + + #[cfg(not(target_arch = "wasm32"))] + let needed_limits = wgpu::Limits::default(); ( surface @@ -52,8 +97,8 @@ pub fn main() { .request_device( &wgpu::DeviceDescriptor { label: None, - features: wgpu::Features::empty(), - limits: wgpu::Limits::default(), + features: adapter_features & wgpu::Features::default(), + limits: needed_limits, }, None, ) @@ -62,20 +107,17 @@ pub fn main() { ) }); - { - let size = window.inner_size(); - - surface.configure( - &device, - &wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format, - width: size.width, - height: size.height, - present_mode: wgpu::PresentMode::Mailbox, - }, - ) - }; + surface.configure( + &device, + &wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format, + width: physical_size.width, + height: physical_size.height, + present_mode: wgpu::PresentMode::Mailbox, + }, + ); + let mut resized = false; // Initialize staging belt and local pool @@ -83,7 +125,7 @@ pub fn main() { let mut local_pool = futures::executor::LocalPool::new(); // Initialize scene and GUI controls - let scene = Scene::new(&mut device); + let scene = Scene::new(&mut device, format); let controls = Controls::new(); // Initialize iced diff --git a/examples/integration_wgpu/src/scene.rs b/examples/integration_wgpu/src/scene.rs index 910d8d8c..fbda1326 100644 --- a/examples/integration_wgpu/src/scene.rs +++ b/examples/integration_wgpu/src/scene.rs @@ -6,8 +6,11 @@ pub struct Scene { } impl Scene { - pub fn new(device: &wgpu::Device) -> Scene { - let pipeline = build_pipeline(device); + pub fn new( + device: &wgpu::Device, + texture_format: wgpu::TextureFormat, + ) -> Scene { + let pipeline = build_pipeline(device, texture_format); Scene { pipeline } } @@ -47,12 +50,14 @@ impl Scene { } } -fn build_pipeline(device: &wgpu::Device) -> wgpu::RenderPipeline { - let vs_module = - device.create_shader_module(&wgpu::include_spirv!("shader/vert.spv")); - - let fs_module = - device.create_shader_module(&wgpu::include_spirv!("shader/frag.spv")); +fn build_pipeline( + device: &wgpu::Device, + texture_format: wgpu::TextureFormat, +) -> wgpu::RenderPipeline { + let (vs_module, fs_module) = ( + device.create_shader_module(&wgpu::include_wgsl!("shader/vert.wgsl")), + device.create_shader_module(&wgpu::include_wgsl!("shader/frag.wgsl")), + ); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { @@ -74,7 +79,7 @@ fn build_pipeline(device: &wgpu::Device) -> wgpu::RenderPipeline { module: &fs_module, entry_point: "main", targets: &[wgpu::ColorTargetState { - format: wgpu::TextureFormat::Bgra8UnormSrgb, + format: texture_format, blend: Some(wgpu::BlendState { color: wgpu::BlendComponent::REPLACE, alpha: wgpu::BlendComponent::REPLACE, diff --git a/examples/integration_wgpu/src/shader/frag.spv b/examples/integration_wgpu/src/shader/frag.spv Binary files differdeleted file mode 100644 index 9d6807c9..00000000 --- a/examples/integration_wgpu/src/shader/frag.spv +++ /dev/null diff --git a/examples/integration_wgpu/src/shader/frag.wgsl b/examples/integration_wgpu/src/shader/frag.wgsl new file mode 100644 index 00000000..a6f61336 --- /dev/null +++ b/examples/integration_wgpu/src/shader/frag.wgsl @@ -0,0 +1,4 @@ +[[stage(fragment)]] +fn main() -> [[location(0)]] vec4<f32> { + return vec4<f32>(1.0, 0.0, 0.0, 1.0); +} diff --git a/examples/integration_wgpu/src/shader/vert.spv b/examples/integration_wgpu/src/shader/vert.spv Binary files differdeleted file mode 100644 index 0cabc9c0..00000000 --- a/examples/integration_wgpu/src/shader/vert.spv +++ /dev/null diff --git a/examples/integration_wgpu/src/shader/vert.wgsl b/examples/integration_wgpu/src/shader/vert.wgsl new file mode 100644 index 00000000..7ef47fb2 --- /dev/null +++ b/examples/integration_wgpu/src/shader/vert.wgsl @@ -0,0 +1,6 @@ +[[stage(vertex)]] +fn main([[builtin(vertex_index)]] in_vertex_index: u32) -> [[builtin(position)]] vec4<f32> { + let x = f32(1 - i32(in_vertex_index)) * 0.5; + let y = f32(1 - i32(in_vertex_index & 1u) * 2) * 0.5; + return vec4<f32>(x, y, 0.0, 1.0); +} diff --git a/examples/pane_grid/Cargo.toml b/examples/pane_grid/Cargo.toml index 03e6cd4a..dfd6dfa9 100644 --- a/examples/pane_grid/Cargo.toml +++ b/examples/pane_grid/Cargo.toml @@ -2,7 +2,7 @@ name = "pane_grid" version = "0.1.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" publish = false [dependencies] diff --git a/examples/pick_list/Cargo.toml b/examples/pick_list/Cargo.toml index a87d7217..4aa4603a 100644 --- a/examples/pick_list/Cargo.toml +++ b/examples/pick_list/Cargo.toml @@ -2,7 +2,7 @@ name = "pick_list" version = "0.1.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" publish = false [dependencies] diff --git a/examples/pokedex/Cargo.toml b/examples/pokedex/Cargo.toml index c64cc85c..e99fc0c3 100644 --- a/examples/pokedex/Cargo.toml +++ b/examples/pokedex/Cargo.toml @@ -2,7 +2,7 @@ name = "pokedex" version = "0.1.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" publish = false [dependencies] diff --git a/examples/progress_bar/Cargo.toml b/examples/progress_bar/Cargo.toml index 4eccbf14..383a9bdd 100644 --- a/examples/progress_bar/Cargo.toml +++ b/examples/progress_bar/Cargo.toml @@ -2,7 +2,7 @@ name = "progress_bar" version = "0.1.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" publish = false [dependencies] diff --git a/examples/qr_code/Cargo.toml b/examples/qr_code/Cargo.toml index 7f2d4e42..2f164df6 100644 --- a/examples/qr_code/Cargo.toml +++ b/examples/qr_code/Cargo.toml @@ -2,7 +2,7 @@ name = "qr_code" version = "0.1.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" publish = false [dependencies] diff --git a/examples/scrollable/Cargo.toml b/examples/scrollable/Cargo.toml index 08502458..610c13b4 100644 --- a/examples/scrollable/Cargo.toml +++ b/examples/scrollable/Cargo.toml @@ -2,7 +2,7 @@ name = "scrollable" version = "0.1.0" authors = ["Clark Moody <clark@clarkmoody.com>"] -edition = "2018" +edition = "2021" publish = false [dependencies] diff --git a/examples/scrollable/README.md b/examples/scrollable/README.md index ed0e31b5..e157d8d7 100644 --- a/examples/scrollable/README.md +++ b/examples/scrollable/README.md @@ -4,8 +4,8 @@ An example showcasing the various size and style options for the Scrollable. All the example code is located in the __[`main`](src/main.rs)__ file. <div align="center"> - <a href="./screenshot.png"> - <img src="./screenshot.png" height="640px"> + <a href="screenshot.png"> + <img src="screenshot.png" height="640px"> </a> </div> diff --git a/examples/scrollable/screenshot.png b/examples/scrollable/screenshot.png Binary files differindex 2d800251..e91fd565 100644 --- a/examples/scrollable/screenshot.png +++ b/examples/scrollable/screenshot.png diff --git a/examples/solar_system/Cargo.toml b/examples/solar_system/Cargo.toml index 327fe0aa..835396b0 100644 --- a/examples/solar_system/Cargo.toml +++ b/examples/solar_system/Cargo.toml @@ -2,7 +2,7 @@ name = "solar_system" version = "0.1.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" publish = false [dependencies] diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index 5f9724f3..12184dd1 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -163,6 +163,10 @@ impl<Message> canvas::Program<Message> for State { Stroke { width: 1.0, color: Color::from_rgba8(0, 153, 255, 0.1), + line_dash: canvas::LineDash { + offset: 0, + segments: &[3.0, 6.0], + }, ..Stroke::default() }, ); diff --git a/examples/stopwatch/Cargo.toml b/examples/stopwatch/Cargo.toml index 9f935951..f623feb9 100644 --- a/examples/stopwatch/Cargo.toml +++ b/examples/stopwatch/Cargo.toml @@ -2,7 +2,7 @@ name = "stopwatch" version = "0.1.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" publish = false [dependencies] diff --git a/examples/styling/Cargo.toml b/examples/styling/Cargo.toml index eb729f93..f771708c 100644 --- a/examples/styling/Cargo.toml +++ b/examples/styling/Cargo.toml @@ -2,7 +2,7 @@ name = "styling" version = "0.1.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" publish = false [dependencies] diff --git a/examples/svg/Cargo.toml b/examples/svg/Cargo.toml index d8f83ac2..f5a6eaa2 100644 --- a/examples/svg/Cargo.toml +++ b/examples/svg/Cargo.toml @@ -2,7 +2,7 @@ name = "svg" version = "0.1.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" publish = false [dependencies] diff --git a/examples/svg/resources/tiger.svg b/examples/svg/resources/tiger.svg index 679edec2..01df5b16 100644 --- a/examples/svg/resources/tiger.svg +++ b/examples/svg/resources/tiger.svg @@ -1,725 +1 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<svg id="svg2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 900" version="1.1"> - <g id="g4" fill="none" transform="matrix(1.7656463,0,0,1.7656463,324.90716,255.00942)"> - <g id="g6" stroke-width="0.17200001" stroke="#000" fill="#FFF"> - <path id="path8" d="m-122.3,84.285s0.1,1.894-0.73,1.875c-0.82-0.019-17.27-48.094-37.8-45.851,0,0,17.78-7.353,38.53,43.976z"/> - </g> - <g id="g10" stroke-width="0.17200001" stroke="#000" fill="#FFF"> - <path id="path12" d="m-118.77,81.262s-0.55,1.816-1.32,1.517c-0.77-0.298,0.11-51.104-19.95-55.978,0,0,19.22-0.864,21.27,54.461z"/> - </g> - <g id="g14" stroke-width="0.17200001" stroke="#000" fill="#FFF"> - <path id="path16" d="m-91.284,123.59s1.636,0.96,1.166,1.64c-0.471,0.67-49.642-12.13-59.102,6.23,0,0,3.68-18.89,57.936-7.87z"/> - </g> - <g id="g18" stroke-width="0.17200001" stroke="#000" fill="#FFF"> - <path id="path20" d="m-94.093,133.8s1.856,0.4,1.622,1.19c-0.233,0.79-50.939,4.13-54.129,24.53,0,0-2.46-19.08,52.507-25.72z"/> - </g> - <g id="g22" stroke-width="0.17200001" stroke="#000" fill="#FFF"> - <path id="path24" d="m-98.304,128.28s1.778,0.66,1.432,1.41-50.998-3.34-57.128,16.37c0,0,0.35-19.24,55.696-17.78z"/> - </g> - <g id="g26" stroke-width="0.17200001" stroke="#000" fill="#FFF"> - <path id="path28" d="m-109.01,110.07s1.31,1.38,0.67,1.9-44.38-25.336-58.53-10.29c0,0,8.74-17.147,57.86,8.39z"/> - </g> - <g id="g30" stroke-width="0.17200001" stroke="#000" fill="#FFF"> - <path id="path32" d="m-116.55,114.26s1.45,1.22,0.88,1.81c-0.58,0.59-46.97-20.148-59.32-3.6,0,0,6.74-18.023,58.44,1.79z"/> - </g> - <g id="g34" stroke-width="0.17200001" stroke="#000" fill="#FFF"> - <path id="path36" d="m-119.15,118.34s1.6,1,1.11,1.67c-0.49,0.66-49.27-13.56-59.25,4.51,0,0,4.22-18.77,58.14-6.18z"/> - </g> - <g id="g38" stroke-width="0.17200001" stroke="#000" fill="#FFF"> - <path id="path40" d="m-108.42,118.95s1.12,1.53,0.42,1.97c-0.7,0.43-40.77-30.818-56.73-17.71,0,0,10.87-15.884,56.31,15.74z"/> - </g> - <g id="g42" stroke-width="0.17200001" stroke="#000" fill="#FFF"> - <path id="path44" d="m-128.2,90s0.6,1.8-0.2,2-29.4-41.8-48.6-34.2c0,0,15.2-11.8,48.8,32.2z"/> - </g> - <g id="g46" stroke-width="0.17200001" stroke="#000" fill="#FFF"> - <path id="path48" d="m-127.5,96.979s0.97,1.629,0.23,1.996c-0.74,0.368-37.72-34.476-54.83-22.914,0,0,12.3-14.8,54.6,20.918z"/> - </g> - <g id="g50" stroke-width="0.17200001" stroke="#000" fill="#FFF"> - <path id="path52" d="m-127.62,101.35s1.12,1.53,0.42,1.97c-0.7,0.43-40.77-30.818-56.73-17.713,0,0,10.87-15.881,56.31,15.743z"/> - </g> - <g id="g54" stroke="#000" fill="#FFF"> - <path id="path56" d="m-129.83,103.06c0.5,6.05,1.49,12.62,3.23,15.74,0,0-3.6,12.4,5.2,25.6,0,0-0.4,7.2,1.2,10.4,0,0,4,8.4,8.8,9.2,3.88,0.65,12.607,3.72,22.468,5.12,0,0,17.132,14.08,13.932,26.88,0,0-0.4,16.4-4,18,0,0,11.6-11.2,2,5.6l-4.4,18.8s25.6-21.6,10-3.2l-10,26s19.6-18.4,12.4-10l-3.2,8.8s43.2-27.2,12.4,2.4c0,0,8-3.6,12.4-0.8,0,0,6.8-1.2,6,0.4,0,0-20.8,10.4-24.4,28.8,0,0,8.4-10,5.2,0.8l0.4,11.6s4-21.6,3.6,16c0,0,19.2-18,7.6,2.8v16.8s15.2-16.4,8.8-3.6c0,0,10-8.8,6,6.4,0,0-0.8,10.4,3.6-0.8,0,0,16-30.6,10-4.4,0,0-0.8,19.2,4,4.4,0,0,0.4,10.4,9.6,17.6,0,0-1.2-50.8,11.6-14.8l4,16.4s2.8-9.2,2.4-14.4l8,8s15.2-22.8,12-9.6c0,0-7.6,16-6,20.8,0,0,16.8-34.8,18-36.4,0,0-2,42.4,8.8,6.4,0,0,5.6,12,2.8,16.4,0,0,8-8,7.2-11.2,0,0,4.6-8.2,7.4,5.4,0,0,1.8,9.4,3.4,6.2,0,0,4,24,5.2,1.2,0,0,1.6-13.6-5.6-25.2,0,0,0.8-3.2-2-7.2,0,0,13.6,21.6,6.4-7.2,0,0,11.201,8,12.401,8,0,0-13.601-23.2-4.801-18.4,0,0-5.2-10.4,12.801,1.6,0,0-16.001-16,1.6-6.4,0,0,7.999,6.4,0.4-3.6,0,0-14.401-16,7.599,2,0,0,11.6,16.4,12.4,19.2,0,0-10-29.2-14.4-32,0,0,8.4-36.4,49.6-20.8,0,0,6.8,17.2,11.2-1.2,0,0,12.8-6.4,24,21.2,0,0,4-13.6,3.2-16.4,0,0,6.8,1.2,6,0,0,0,13.2,4.4,14.4,3.6,0,0,6.8,6.8,7.2,3.2,0,0,9.2,2.8,7.2-0.8,0,0,8.8,15.6,9.2,19.2l2.4-14,2,2.8s1.6-7.6,0.8-8.8,20,6.8,24.8,27.6l2,8.4s6-14.8,4.4-18.8c0,0,5.2,0.8,5.6,5.2,0,0,4-23.2-0.8-29.2,0,0,4.4-0.8,5.6,2.8v-7.2s7.2,0.8,7.2-1.6c0,0,4.4-4,6.4,0.8,0,0-12.4-35.2,6-16,0,0,7.2,10.8,3.6-8s-7.6-20.4-2.8-20.8c0,0,0.8-3.6-1.2-5.2s1.2,0,1.2,0,4.8,4-0.4-18c0,0,6.4,1.6-5.6-27.6,0,0,2.8-2.4-1.2-10.8,0,0,8,4.4,10.8,2.8,0,0-0.4-1.6-3.6-5.6,0,0-21.6-54.8-1.2-32.8,0,0,11.85,13.55,5.45-9.25,0,0-9.11-24.009-8.33-28.305l-429.55,23.015z"/> - </g> - <g id="g58" stroke="#000" fill="#cc7226"> - <path id="path60" d="m299.72,80.245c0.62,0.181,2.83,1.305,4.08,2.955,0,0,6.8,10.8,1.6-7.6,0,0-9.2-28.8-0.4-17.6,0,0,6,7.2,2.8-6.4-3.86-16.427-6.4-22.8-6.4-22.8s11.6,4.8-15.2-34.8l8.8,3.6s-19.6-39.6-41.2-44.8l-8-6s38.4-38,25.6-74.8c0,0-6.8-5.2-16.4,4,0,0-6.4,4.8-12.4,3.2,0,0-30.8,1.2-32.8,1.2s-36.8-37.2-102.4-19.6c0,0-5.2,2-9.599,0.8,0,0-18.401-16-67.201,6.8,0,0-10,2-11.6,2s-4.4,0-12.4,6.4-8.4,7.2-10.4,8.8c0,0-16.4,11.2-21.2,12,0,0-11.6,6.4-16,16.4l-3.6,1.2s-1.6,7.2-2,8.4c0,0-4.8,3.6-5.6,9.2,0,0-8.8,6-8.4,10.4,0,0-1.6,5.2-2.4,10,0,0-7.2,4.8-6.4,7.6,0,0-7.6,14-6.4,20.8,0,0-6.4-0.4-9.2,2,0,0-0.8,4.8-2.4,5.2,0,0-2.8,1.2-0.4,5.2,0,0-1.6,2.8-2,4.4,0,0,0.8,2.8-3.6,8.4,0,0-6.4,18.8-4.4,24,0,0,0.4,4.8-2.4,6.4,0,0-3.6-0.4,4.8,11.6,0,0,0.8,1.2-2.4,3.6,0,0-17.2,3.6-19.6,20,0,0-13.6,14.8-13.6,20,0,2.305,0.27,5.452,0.97,10.06,0,0-0.57,8.34,27.03,9.14s402.72-31.355,402.72-31.355z"/> - </g> - <g id="g62" fill="#cc7226"> - <path id="path64" d="m-115.6,102.6c-25-39.4-10.6,17-10.6,17,8.8,34.4,138.4-3.2,138.4-3.2s168.8-30.4,180-34.4,106.4,2.4,106.4,2.4l-5.6-16.8c-64.8-46.4-84-23.2-97.6-27.2s-11.2,5.6-14.4,6.4-42.4-24-48.8-23.2-31.74-22.951-16.8,8.8c16,34-58.4,39.2-75.2,28s7.2,18.4,7.2,18.4c18.4,20-16,3.2-16,3.2-34.4-12.8-58.4,12.8-61.6,13.6s-8,4-8.8-2.4-8.31-23.101-40,3.2c-20,16.6-33.8-5.4-33.8-5.4l-2.8,11.6z"/> - </g> - <g id="g66" fill="#e87f3a"> - <path id="path68" d="m133.51,25.346c-6.4,0.8-31.77-22.939-16.8,8.8,16.6,35.2-58.4,39.2-75.2,28-16.801-11.2,7.2,18.4,7.2,18.4,18.4,20.004-16.001,3.2-16.001,3.2-34.4-12.8-58.4,12.8-61.6,13.6s-8,4.004-8.8-2.4c-0.8-6.4-8.179-22.934-40,3.2-21.236,17.344-34.729-4.109-34.729-4.109l-3.2,10.113c-25-39.804-9.93,18.51-9.93,18.51,8.81,34.4,139.06-4.51,139.06-4.51s168.8-30.404,180-34.404,105.53,2.327,105.53,2.327l-5.53-17.309c-64.8-46.4-83.2-22.618-96.8-26.618s-11.2,5.6-14.4,6.4-42.4-24-48.8-23.2z"/> - </g> - <g id="g70" fill="#ea8c4d"> - <path id="path72" d="m134.82,27.091c-6.4,0.8-31.14-23.229-16.8,8.8,16.2,36.201-58.401,39.201-75.201,28.001s7.2,18.4,7.2,18.4c18.4,19.998-16,3.2-16,3.2-34.4-12.8-58.401,12.8-61.601,13.6s-8,3.998-8.8-2.4c-0.8-6.4-8.048-22.767-40,3.2-22.473,18.088-35.658-2.818-35.658-2.818l-3.6,8.616c-23.8-38.998-9.25,20.02-9.25,20.02,8.8,34.4,139.71-5.82,139.71-5.82s168.8-30.398,180-34.398,104.65,2.254,104.65,2.254l-5.45-17.818c-64.8-46.4-82.4-22.037-96-26.037s-11.2,5.6-14.4,6.401c-3.2,0.8-42.4-24.001-48.8-23.201z"/> - </g> - <g id="g74" fill="#ec9961"> - <path id="path76" d="m136.13,28.837c-6.4,0.8-31.13-23.232-16.8,8.8,16.8,37.556-58.936,38.845-75.202,28-16.8-11.2,7.2,18.4,7.2,18.4,18.4,20.003-16,3.2-16,3.2-34.4-12.8-58.4,12.803-61.6,13.603s-8,4-8.8-2.403c-0.8-6.4-7.917-22.598-40.001,3.203-23.709,18.83-36.587-1.53-36.587-1.53l-4,7.13c-21.8-36.803-8.58,21.52-8.58,21.52,8.8,34.4,140.37-7.12,140.37-7.12s168.8-30.403,180-34.403,103.78,2.182,103.78,2.182l-5.38-18.327c-64.8-46.401-81.6-21.455-95.2-25.455s-11.2,5.6-14.4,6.4-42.4-24-48.8-23.2z"/> - </g> - <g id="g78" fill="#eea575"> - <path id="path80" d="m137.44,30.583c-6.4,0.8-30.63-23.454-16.8,8.8,16.8,39.2-58.403,39.2-75.203,28s7.2,18.4,7.2,18.4c18.4,19.997-16,3.2-16,3.2-34.4-12.8-58.4,12.797-61.6,13.597s-8,4-8.8-2.4c-0.8-6.397-7.785-22.428-40,3.2-24.946,19.58-37.507-0.23-37.507-0.23l-4.4,5.63c-19.8-34.798-7.91,23.04-7.91,23.04,8.8,34.4,141.02-8.44,141.02-8.44s168.8-30.397,180-34.397,102.91,2.109,102.91,2.109l-5.31-18.837c-64.8-46.4-80.8-20.872-94.4-24.872s-11.2,5.6-14.4,6.4-42.4-24-48.8-23.2z"/> - </g> - <g id="g82" fill="#f1b288"> - <path id="path84" d="m138.75,32.328c-6.4,0.8-32.37-22.651-16.8,8.8,19.2,38.8-58.404,39.2-75.204,28s7.2,18.4,7.2,18.4c18.4,20.002-16,3.2-16,3.2-34.4-12.8-58.4,12.802-61.6,13.602s-8,4-8.8-2.4c-0.8-6.402-7.654-22.265-40,3.2-26.182,20.33-38.436,1.05-38.436,1.05l-4.8,4.15c-18-33.202-7.24,24.54-7.24,24.54,8.8,34.4,141.68-9.74,141.68-9.74s168.8-30.402,180-34.402,102.03,2.036,102.03,2.036l-5.23-19.345c-64.8-46.4-80-20.291-93.6-24.291s-11.2,5.6-14.4,6.4-42.4-24-48.8-23.2z"/> - </g> - <g id="g86" fill="#f3bf9c"> - <path id="path88" d="m140.06,34.073c-6.4,0.8-32.75-22.46-16.8,8.8,20.4,40.001-58.405,39.201-75.205,28.001s7.2,18.4,7.2,18.4c18.4,19.996-16,3.2-16,3.2-34.4-12.8-58.4,12.796-61.6,13.596s-8,4-8.8-2.4c-0.8-6.396-7.523-22.092-40,3.2-27.419,21.08-39.365,2.35-39.365,2.35l-5.2,2.65c-16-30.196-6.56,26.06-6.56,26.06,8.8,34.4,142.32-11.06,142.32-11.06s168.8-30.396,180-34.396,101.16,1.963,101.16,1.963l-5.16-19.854c-64.8-46.4-79.2-19.709-92.8-23.709-13.6-4.001-11.2,5.6-14.4,6.4s-42.4-24.001-48.8-23.201z"/> - </g> - <g id="g90" fill="#f5ccb0"> - <path id="path92" d="m141.36,35.819c-6.4,0.8-33.84-21.875-16.8,8.8,22,39.6-58.396,39.2-75.196,28s7.2,18.4,7.2,18.4c18.4,20.001-16,3.2-16,3.2-34.4-12.8-58.4,12.801-61.6,13.601s-8,4-8.8-2.4c-0.8-6.401-7.391-21.928-40,3.2-28.655,21.82-40.294,3.64-40.294,3.64l-5.6,1.16c-14.4-28.401-5.89,27.56-5.89,27.56,8.8,34.4,142.98-12.36,142.98-12.36s168.8-30.401,180-34.401,100.3,1.891,100.3,1.891l-5.1-20.364c-64.8-46.4-78.4-19.127-92-23.127s-11.2,5.6-14.4,6.4-42.4-24-48.8-23.2z"/> - </g> - <g id="g94" fill="#f8d8c4"> - <path id="path96" d="m142.67,37.565c-6.4,0.8-33.84-21.876-16.8,8.8,22,39.6-58.396,39.2-75.196,28s7.2,18.4,7.2,18.4c18.4,19.995-16,3.2-16,3.2-34.401-12.8-58.401,12.795-61.601,13.595s-8,4-8.8-2.4-7.259-21.755-40,3.2c-29.891,22.57-41.213,4.93-41.213,4.93l-6-0.33c-13.61-26.396-5.22,29.08-5.22,29.08,8.8,34.4,143.63-13.68,143.63-13.68s168.8-30.395,180-34.395,99.42,1.818,99.42,1.818l-5.01-20.873c-64.81-46.4-77.61-18.545-91.21-22.545s-11.2,5.6-14.4,6.4-42.4-24-48.8-23.2z"/> - </g> - <g id="g98" fill="#fae5d7"> - <path id="path100" d="m143.98,39.31c-6.4,0.8-33.45-22.087-16.8,8.8,22,40.8-58.397,39.2-75.197,28s7.2,18.4,7.2,18.4c18.4,20-16,3.2-16,3.2-34.4-12.8-58.4,12.8-61.6,13.6-3.201,0.8-8.001,4-8.801-2.4s-7.128-21.592-40,3.2c-31.127,23.31-42.142,6.22-42.142,6.22l-6.4-1.82c-13-24-4.55,30.58-4.55,30.58,8.8,34.4,144.29-14.98,144.29-14.98s168.8-30.4,180-34.4,98.55,1.746,98.55,1.746l-4.95-21.382c-64.8-46.401-76.8-17.964-90.4-21.964s-11.2,5.6-14.4,6.4-42.4-24-48.8-23.2z"/> - </g> - <g id="g102" fill="#fcf2eb"> - <path id="path104" d="m145.29,41.055c-6.4,0.8-32.37-22.644-16.8,8.8,21.2,42.801-58.398,39.201-75.198,28.001s7.2,18.4,7.2,18.4c18.4,20.004-16,3.2-16,3.2-34.4-12.8-58.4,12.804-61.6,13.604s-8,4-8.8-2.4-6.997-21.428-40,3.2c-32.365,24.05-43.072,7.5-43.072,7.5l-6.8-3.3c-12.8-23.204-3.87,32.09-3.87,32.09,8.8,34.4,144.94-16.29,144.94-16.29s168.8-30.4,180-34.404c11.2-4,97.67,1.674,97.67,1.674l-4.87-21.893c-64.8-46.4-76-17.381-89.6-21.381-13.6-4.001-11.2,5.6-14.4,6.4s-42.4-24.001-48.8-23.201z"/> - </g> - <g id="g106" fill="#FFF"> - <path id="path108" d="m-115.8,119.6c-12.8-22-3.2,33.6-3.2,33.6,8.8,34.4,145.6-17.6,145.6-17.6s168.8-30.4,180-34.4,96.8,1.6,96.8,1.6l-4.8-22.4c-64.8-46.4-75.2-16.8-88.8-20.8s-11.2,5.6-14.4,6.4-42.4-24-48.8-23.2-31.62-23.007-16.8,8.8c22.23,47.707-60.759,37.627-75.2,28-16.8-11.2,7.2,18.4,7.2,18.4,18.4,20-16,3.2-16,3.2-34.4-12.8-58.4,12.8-61.6,13.6s-8,4-8.8-2.4-6.865-21.256-40,3.2c-33.6,24.8-44,8.8-44,8.8l-7.2-4.8z"/> - </g> - <g id="g110" fill="#000"> - <path id="path112" d="m-74.2,149.6s-7.2,11.6,13.6,24.8c0,0,1.4,1.4-16.6-2.8,0,0-6.2-2-7.8-12.4,0,0-4.8-4.4-9.6-10s20.4,0.4,20.4,0.4z"/> - </g> - <g id="g114" fill="#CCC"> - <path id="path116" d="m65.8,102s17.698,26.82,17.1,31.6c-1.3,10.4-1.5,20,1.7,24,3.201,4,12.001,37.2,12.001,37.2s-0.4,1.2,11.999-36.8c0,0,11.6-16-8.4-34.4,0,0-35.2-28.8-34.4-21.6z"/> - </g> - <g id="g118" fill="#000"> - <path id="path120" d="m-54.2,176.4s11.2,7.2-3.2,38.4l6.4-2.4s-0.8,11.2-4,13.6l7.2-3.2s4.8,8,0.8,12.8c0,0,16.8,8,16,14.4,0,0,6.4-8,2.4-14.4s-11.2-2.4-10.4-20.8l-8.8,3.2s5.6-8.8,5.6-15.2l-8,2.4s15.469-26.58,4.8-28c-6-0.8-8.8-0.8-8.8-0.8z"/> - </g> - <g id="g122" fill="#CCC"> - <path id="path124" d="m-21.8,193.2s2.8-4.4,0-3.6-34,15.6-40,25.2c0,0,34.4-24.4,40-21.6z"/> - </g> - <g id="g126" fill="#CCC"> - <path id="path128" d="m-11.4,201.2s2.8-4.4,0-3.6-34,15.6-40,25.2c0,0,34.4-24.4,40-21.6z"/> - </g> - <g id="g130" fill="#CCC"> - <path id="path132" d="m1.8,186s2.8-4.4,0-3.6-34,15.6-40,25.2c0,0,34.4-24.4,40-21.6z"/> - </g> - <g id="g134" fill="#CCC"> - <path id="path136" d="m-21.4,229.6s0-6-2.8-5.2-38.8,18.4-44.8,28c0,0,42-25.6,47.6-22.8z"/> - </g> - <g id="g138" fill="#CCC"> - <path id="path140" d="m-20.2,218.8s1.2-4.8-1.6-4c-2,0-28.4,11.6-34.4,21.2,0,0,29.6-21.6,36-17.2z"/> - </g> - <g id="g142" fill="#CCC"> - <path id="path144" d="m-34.6,266.4-10,7.6s10.4-7.6,14-6.4c0,0-6.8,11.2-7.6,16.4,0,0,10.4-12.8,16-12.4,0,0,7.6,0.4,7.6,11.2,0,0,5.6-10.4,8.8-10,0,0,1.2,6.4,0,13.2,0,0,4-7.6,8-6,0,0,6.4-2,5.6,9.6,0,0,0,10.4-0.8,13.2,0,0,5.6-26.4,8-26.8,0,0,8-1.2,12.8,7.6,0,0-4-7.6,0.8-5.6,0,0,10.8,1.6,14,8.4,0,0-6.8-12-1.2-8.8l8,6.4s8.4,21.2,10.4,22.8c0,0-7.6-21.6-6-21.6,0,0-2-12,3.2,2.8,0,0-3.2-14,2.4-13.2s10,10.8,18.4,8.4c0,0,9.601,5.6,11.601-63.6l-124,46.8z"/> - </g> - <g id="g146" fill="#000"> - <path id="path148" d="m-29.8,173.6s14.8-6,54.8,0c0,0,7.2,0.4,14-8.4s33.6-16,40-14l9.601,6.4,0.8,1.2s12.399,10.4,12.799,18-14.399,55.6-24,71.6c-9.6,16-19.2,28.4-38.4,26,0,0-20.8-4-46.4,0,0,0-29.2-1.6-32-9.6s11.2-23.2,11.2-23.2,4.4-8.4,3.2-22.8-0.8-42.4-5.6-45.2z"/> - </g> - <g id="g150" fill="#e5668c"> - <path id="path152" d="M-7.8,175.6c8.4,18.4-21.2,83.6-21.2,83.6-2,1.6,12.66,7.65,22.8,5.2,10.946-2.64,51.2,1.6,51.2,1.6,23.6-15.6,36.4-60,36.4-60s10.401-24-7.2-27.2c-17.6-3.2-82-3.2-82-3.2z"/> - </g> - <g id="g154" fill="#b23259"> - <path id="path156" d="m-9.831,206.5c3.326-12.79,4.91-24.59,2.031-30.9,0,0,62.4,6.4,73.6-14.4,4.241-7.87,19.001,22.8,18.6,32.4,0,0-63,14.4-77.8,3.2l-16.431,9.7z"/> - </g> - <g id="g158" fill="#a5264c"> - <path id="path160" d="m-5.4,222.8s2,7.2-0.4,11.2c0,0-1.6,0.8-2.8,1.2,0,0,1.2,3.6,7.2,5.2,0,0,2,4.4,4.4,4.8s7.2,6,11.2,4.8,15.2-5.2,15.2-5.2,5.6-3.2,14.4,0.4c0,0,2.375-0.8,2.8-4.8,0.5-4.7,3.6-8.4,5.6-10.4s11.6-14.8,10.4-15.2-68,8-68,8z"/> - </g> - <g id="g162" stroke="#000" fill="#ff727f"> - <path id="path164" d="m-9.8,174.4s-2.8,22.4,0.4,30.8,2.4,10.4,1.6,14.4,3.6,14,9.2,20l12,1.6s15.2-3.6,24.4-0.8c0,0,8.994,1.34,12.4-13.6,0,0,4.8-6.4,12-9.2s14.4-44.4,10.4-52.4-18.4-12.4-34.4,3.2-18-1.2-48,6z"/> - </g> - <g id="g166" stroke-width="0.5" stroke="#000" fill="#FFC"> - <path id="path168" d="m-8.2,249.2s-0.8-2-5.2-2.4c0,0-22.4-3.6-30.8-16,0,0-6.8-5.6-2.4,6,0,0,10.4,20.4,17.2,23.2,0,0,16.4,4,21.2-10.8z"/> - </g> - <g id="g170" fill="#cc3f4c"> - <path id="path172" d="m71.742,185.23c0.659-7.91,2.612-16.52,0.858-20.03-6.446-12.89-23.419-7.5-34.4,3.2-16,15.6-18-1.2-48,6,0,0-1.745,13.96-0.905,23.98,0,0,37.305-11.58,38.105-5.98,0,0,1.6-3.2,10.8-3.2s31.942-1.17,33.542-3.97z"/> - </g> - <g id="g174" stroke-width="2" stroke="#a51926"> - <path id="path176" d="m28.6,175.2s4.8,4.8,1.2,14.4c0,0-14.4,16-12.4,30"/> - </g> - <g id="g178" stroke-width="0.5" stroke="#000" fill="#FFC"> - <path id="path180" d="m-19.4,260s-4.4-12.8,4.4-6l3.6,3.6c-1.2,1.6-6.8,5.6-8,2.4z"/> - </g> - <g id="g182" stroke-width="0.5" stroke="#000" fill="#FFC"> - <path id="path184" d="m-14.36,261.2s-3.52-10.24,3.52-4.8l2.88,2.88c-4.56,1.28,0,3.84-6.4,1.92z"/> - </g> - <g id="g186" stroke-width="0.5" stroke="#000" fill="#FFC"> - <path id="path188" d="m-9.56,261.2s-3.52-10.24,3.52-4.8l2.88,2.88c-3.36,1.28,0,3.84-6.4,1.92z"/> - </g> - <g id="g190" stroke-width="0.5" stroke="#000" fill="#FFC"> - <path id="path192" d="m-2.96,261.4s-3.52-10.24,3.52-4.8c0,0,4.383,2.33,2.881,2.88-2.961,1.08,0,3.84-6.401,1.92z"/> - </g> - <g id="g194" stroke-width="0.5" stroke="#000" fill="#FFC"> - <path id="path196" d="m3.52,261.32s-3.52-10.24,3.521-4.8l2.88,2.88c-0.96,1.28,0,3.84-6.401,1.92z"/> - </g> - <g id="g198" stroke-width="0.5" stroke="#000" fill="#FFC"> - <path id="path200" d="m10.2,262s-4.8-12.4,4.4-6l3.6,3.6c-1.2,1.6,0,4.8-8,2.4z"/> - </g> - <g id="g202" stroke-width="2" stroke="#a5264c"> - <path id="path204" d="m-18.2,244.8s13.2-2.8,19.2,0.4c0,0,6,1.2,7.2,0.8s4.4-0.8,4.4-0.8"/> - </g> - <g id="g206" stroke-width="2" stroke="#a5264c"> - <path id="path208" d="m15.8,253.6s12-13.6,24-9.2c7.016,2.57,6-0.8,6.8-3.6s1-7,6-10"/> - </g> - <g id="g210" stroke-width="0.5" stroke="#000" fill="#FFC"> - <path id="path212" d="m33,237.6s-4-10.8-6.8,2-6,16.4-7.6,19.2c0,0,0,5.2,8.4,4.8,0,0,10.8-0.4,11.2-3.2s-1.2-14.4-5.2-22.8z"/> - </g> - <g id="g214" stroke-width="2" stroke="#a5264c"> - <path id="path216" d="m47,244.8s3.6-2.4,6-1.2"/> - </g> - <g id="g218" stroke-width="2" stroke="#a5264c"> - <path id="path220" d="m53.5,228.4s2.9-4.9,7.7-5.7"/> - </g> - <g id="g222" fill="#b2b2b2"> - <path id="path224" d="m-25.8,265.2s18,3.2,22.4,1.6l0.4,2-20.8-1.2s-11.6-5.6-2-2.4z"/> - </g> - <g id="g226" stroke-width="0.5" stroke="#000" fill="#FFC"> - <path id="path228" d="m-11.8,172,19.6,0.8s7.2,30.8,3.6,38.4c0,0-1.2,2.8-4-2.8,0,0-18.4-32.8-21.6-34.8s1.2-1.6,2.4-1.6z"/> - </g> - <g id="g230" stroke-width="0.5" stroke="#000" fill="#FFC"> - <path id="path232" d="m-88.9,169.3s8.9,1.7,21.5,4.3c0,0,4.8,22.4,8,27.2s-0.4,4.8-4,2-18.4-16.8-20.4-21.2-5.1-12.3-5.1-12.3z"/> - </g> - <g id="g234" stroke-width="0.5" stroke="#000" fill="#FFC"> - <path id="path236" d="m-67.039,173.82s5.8,1.55,6.809,3.76c1.008,2.22-1.202,5.51-1.202,5.51s-1,3.31-2.202,1.15c-1.202-2.17-4.074-9.83-3.405-10.42z"/> - </g> - <g id="g238" fill="#000"> - <path id="path240" d="m-67,173.6s3.6,5.2,7.2,5.2,3.982-0.41,6.8,0.2c4.6,1,4.2-1,10.8,0.2,2.64,0.48,5.2-0.4,8,0.8s6,0.4,7.2-1.6,6-6.2,6-6.2-12.8,1.8-15.6,2.6c0,0-22.4,1.2-30.4-1.2z"/> - </g> - <g id="g242" stroke-width="0.5" stroke="#000" fill="#FFC"> - <path id="path244" d="m-22.4,173.8s-6.45,3.5-6.85,5.9,5.25,6.1,5.25,6.1,2.75,4.6,3.35,2.2-0.95-13.8-1.75-14.2z"/> - </g> - <g id="g246" stroke-width="0.5" stroke="#000" fill="#FFC"> - <path id="path248" d="m-59.885,179.26s7.007,11.19,7.224-0.02c0,0,0.557-1.26-1.203-1.28-6.075-0.07-4.554-4.18-6.021,1.3z"/> - </g> - <g id="g250" stroke-width="0.5" stroke="#000" fill="#FFC"> - <path id="path252" d="m-52.707,179.51s7.921,11.19,7.285-0.09c0,0,0.007-0.33-1.746-0.48-4.747-0.42-4.402-4.94-5.539,0.57z"/> - </g> - <g id="g254" stroke-width="0.5" stroke="#000" fill="#FFC"> - <path id="path256" d="m-45.494,179.52s7.96,10.63,7.291,0.96c0,0,0.119-1.23-1.535-1.53-3.892-0.71-4.103-3.95-5.756,0.57z"/> - </g> - <g id="g258" stroke-width="0.5" stroke="#000" fill="#FFC"> - <path id="path260" d="m-38.618,179.6s7.9,11.56,8.248,1.78c0,0,1.644-1.38-0.102-1.6-5.818-0.74-5.02-5.19-8.146-0.18z"/> - </g> - <g id="g262" fill="#e5e5b2"> - <path id="path264" d="m-74.792,183.13-7.658-1.53c-2.6-5-4.7-11.15-4.7-11.15s6.35,1,18.85,3.8c0,0,0.876,3.32,2.348,9.11l-8.84-0.23z"/> - </g> - <g id="g266" fill="#e5e5b2"> - <path id="path268" d="m-9.724,178.47c-1.666-2.51-2.983-4.26-3.633-4.67-3.013-1.88,1.13-1.51,2.259-1.51l18.454,0.76s0.524,2.24,1.208,5.63c0,0-10.088-2.01-18.288-0.21z"/> - </g> - <g id="g270" fill="#cc7226"> - <path id="path272" d="m43.88,40.321c27.721,3.96,53.241-31.68,55.001-41.361,1.759-9.68-8.36-21.56-8.36-21.56,1.32-3.08-3.52-17.16-8.8-26.4s-21.181-8.266-38.721-9.24c-15.84-0.88-34.32,22.44-35.64,24.2s4.84,40.041,6.16,45.761-1.32,32.12-1.32,32.12c34.24-9.1,3.96-7.48,31.68-3.52z"/> - </g> - <g id="g274" fill="#ea8e51"> - <path id="path276" d="m8.088-33.392c-1.296,1.728,4.752,39.313,6.048,44.929s-1.296,31.536-1.296,31.536c32.672-8.88,3.888-7.344,31.104-3.456,27.217,3.888,52.273-31.104,54.001-40.609,1.728-9.504-8.208-21.168-8.208-21.168,1.296-3.024-3.456-16.848-8.64-25.92s-20.795-8.115-38.017-9.072c-15.552-0.864-33.696,22.032-34.992,23.76z"/> - </g> - <g id="g278" fill="#efaa7c"> - <path id="path280" d="m8.816-32.744c-1.272,1.696,4.664,38.585,5.936,44.097s-1.272,30.952-1.272,30.952c31.404-9.16,3.816-7.208,30.528-3.392,26.713,3.816,51.305-30.528,53.001-39.857,1.696-9.328-8.056-20.776-8.056-20.776,1.272-2.968-3.392-16.536-8.48-25.44s-20.41-7.965-37.313-8.904c-15.264-0.848-33.072,21.624-34.344,23.32z"/> - </g> - <g id="g282" fill="#f4c6a8"> - <path id="path284" d="m9.544-32.096c-1.248,1.664,4.576,37.857,5.824,43.265s-1.248,30.368-1.248,30.368c29.436-9.04,3.744-7.072,29.952-3.328,26.209,3.744,50.337-29.952,52.001-39.104,1.664-9.153-7.904-20.385-7.904-20.385,1.248-2.912-3.328-16.224-8.32-24.96s-20.025-7.815-36.609-8.736c-14.976-0.832-32.448,21.216-33.696,22.88z"/> - </g> - <g id="g286" fill="#f9e2d3"> - <path id="path288" d="m10.272-31.448c-1.224,1.632,4.488,37.129,5.712,42.433s-1.224,29.784-1.224,29.784c27.868-8.92,3.672-6.936,29.376-3.264,25.705,3.672,49.369-29.376,51.001-38.353,1.632-8.976-7.752-19.992-7.752-19.992,1.224-2.856-3.264-15.912-8.16-24.48s-19.64-7.665-35.905-8.568c-14.688-0.816-31.824,20.808-33.048,22.44z"/> - </g> - <g id="g290" fill="#FFF"> - <path id="path292" d="M44.2,36.8c25.2,3.6,48.401-28.8,50.001-37.6s-7.6-19.6-7.6-19.6c1.2-2.8-3.201-15.6-8.001-24s-19.254-7.514-35.2-8.4c-14.4-0.8-31.2,20.4-32.4,22s4.4,36.4,5.6,41.6-1.2,29.2-1.2,29.2c25.5-8.6,3.6-6.8,28.8-3.2z"/> - </g> - <g id="g294" fill="#CCC"> - <path id="path296" d="m90.601,2.8s-27.801,7.6-39.401,6c0,0-15.8-6.6-24.6,15.2,0,0-3.6,7.2-5.6,9.2s69.601-30.4,69.601-30.4z"/> - </g> - <g id="g298" fill="#000"> - <path id="path300" d="m94.401,0.6s-29.001,12.2-39.001,11.8c0,0-16.4-4.6-24.8,10,0,0-8.4,9.2-11.6,10.8,0,0-0.4,1.6,6-2.4l10.4,5.2s14.8,9.6,24.4-6.4c0,0,4-11.2,4-13.2s21.2-7.6,22.801-8c1.6-0.4,8.2-4.6,7.8-7.8z"/> - </g> - <g id="g302" fill="#99cc32"> - <path id="path304" d="m47,36.514c-6.872,0-15.245-3.865-15.245-10.114,0-6.248,8.373-12.513,15.245-12.513,6.874,0,12.446,5.065,12.446,11.313,0,6.249-5.572,11.314-12.446,11.314z"/> - </g> - <g id="g306" fill="#659900"> - <path id="path308" d="m43.377,19.83c-4.846,0.722-9.935,2.225-9.863,2.009,1.54-4.619,7.901-7.952,13.486-7.952,4.296,0,8.084,1.978,10.32,4.988,0,0-5.316-0.33-13.943,0.955z"/> - </g> - <g id="g310" fill="#FFF"> - <path id="path312" d="m55.4,19.6s-4.4-3.2-4.4-1c0,0,3.6,4.4,4.4,1z"/> - </g> - <g id="g314" fill="#000"> - <path id="path316" d="m45.4,27.726c-2.499,0-4.525-2.026-4.525-4.526,0-2.499,2.026-4.525,4.525-4.525,2.5,0,4.526,2.026,4.526,4.525,0,2.5-2.026,4.526-4.526,4.526z"/> - </g> - <g id="g318" fill="#cc7226"> - <path id="path320" d="m-58.6,14.4s-3.2-21.2-0.8-25.6c0,0,10.8-10,10.4-13.6,0,0-0.4-18-1.6-18.8s-8.8-6.8-14.8-0.4c0,0-10.4,18-9.6,24.4v2s-7.6-0.4-9.2,1.6c0,0-1.2,5.2-2.4,5.6,0,0-2.8,2.4-0.8,5.2,0,0-2,2.4-1.6,6.4l7.6,4s2,14.4,12.8,19.6c4.836,2.329,8-4.4,10-10.4z"/> - </g> - <g id="g322" fill="#FFF"> - <path id="path324" d="m-59.6,12.56s-2.88-19.08-0.72-23.04c0,0,9.72-9,9.36-12.24,0,0-0.36-16.2-1.44-16.92s-7.92-6.12-13.32-0.36c0,0-9.36,16.2-8.64,21.96v1.8s-6.84-0.36-8.28,1.44c0,0-1.08,4.68-2.16,5.04,0,0-2.52,2.16-0.72,4.68,0,0-1.8,2.16-1.44,5.76l6.84,3.6s1.8,12.96,11.52,17.64c4.352,2.095,7.2-3.96,9-9.36z"/> - </g> - <g id="g326" fill="#eb955c"> - <path id="path328" d="m-51.05-42.61c-1.09-0.86-8.58-6.63-14.43-0.39,0,0-10.14,17.55-9.36,23.79v1.95s-7.41-0.39-8.97,1.56c0,0-1.17,5.07-2.34,5.46,0,0-2.73,2.34-0.78,5.07,0,0-1.95,2.34-1.56,6.24l7.41,3.9s1.95,14.04,12.48,19.11c4.714,2.27,7.8-4.29,9.75-10.14,0,0-3.12-20.67-0.78-24.96,0,0,10.53-9.75,10.14-13.26,0,0-0.39-17.55-1.56-18.33z"/> - </g> - <g id="g330" fill="#f2b892"> - <path id="path332" d="m-51.5-41.62c-0.98-0.92-8.36-6.46-14.06-0.38,0,0-9.88,17.1-9.12,23.18v1.9s-7.22-0.38-8.74,1.52c0,0-1.14,4.94-2.28,5.32,0,0-2.66,2.28-0.76,4.94,0,0-1.9,2.28-1.52,6.08l7.22,3.8s1.9,13.68,12.16,18.62c4.594,2.212,7.6-4.18,9.5-9.88,0,0-3.04-20.14-0.76-24.32,0,0,10.26-9.5,9.88-12.92,0,0-0.38-17.1-1.52-17.86z"/> - </g> - <g id="g334" fill="#f8dcc8"> - <path id="path336" d="m-51.95-40.63c-0.87-0.98-8.14-6.29-13.69-0.37,0,0-9.62,16.65-8.88,22.57v1.85s-7.03-0.37-8.51,1.48c0,0-1.11,4.81-2.22,5.18,0,0-2.59,2.22-0.74,4.81,0,0-1.85,2.22-1.48,5.92l7.03,3.7s1.85,13.32,11.84,18.13c4.473,2.154,7.4-4.07,9.25-9.62,0,0-2.96-19.61-0.74-23.68,0,0,9.99-9.25,9.62-12.58,0,0-0.37-16.65-1.48-17.39z"/> - </g> - <g id="g338" fill="#FFF"> - <path id="path340" d="m-59.6,12.46s-2.88-18.98-0.72-22.94c0,0,9.72-9,9.36-12.24,0,0-0.36-16.2-1.44-16.92-0.76-1.04-7.92-6.12-13.32-0.36,0,0-9.36,16.2-8.64,21.96v1.8s-6.84-0.36-8.28,1.44c0,0-1.08,4.68-2.16,5.04,0,0-2.52,2.16-0.72,4.68,0,0-1.8,2.16-1.44,5.76l6.84,3.6s1.8,12.96,11.52,17.64c4.352,2.095,7.2-4.06,9-9.46z"/> - </g> - <g id="g342" fill="#CCC"> - <path id="path344" d="m-62.7,6.2s-21.6-10.2-22.5-11c0,0,9.1,8.2,9.9,8.2s12.6,2.8,12.6,2.8z"/> - </g> - <g id="g346" fill="#000"> - <path id="path348" d="m-79.8,0s18.4,3.6,18.4,8c0,2.912-0.243,16.331-5.6,14.8-8.4-2.4-4.8-16.8-12.8-22.8z"/> - </g> - <g id="g350" fill="#99cc32"> - <path id="path352" d="m-71.4,3.8s8.978,1.474,10,4.2c0.6,1.6,1.263,9.908-4.2,11-4.552,0.911-6.782-9.31-5.8-15.2z"/> - </g> - <g id="g354" fill="#000"> - <path id="path356" d="m14.595,46.349c-0.497-1.742,0.814-1.611,2.605-2.149,2-0.6,14.2-4.4,15-7s14,1.8,14,1.8c1.8,0.8,6.2,3.4,6.2,3.4,4.8,1.2,11.4,1.6,11.4,1.6,2.4,1,5.8,3.8,5.8,3.8,14.6,10.2,27.001,3,27.001,3,19.999-6.6,13.999-23.8,13.999-23.8-3-9,0.2-12.4,0.2-12.4,0.2-3.8,7.4,2.6,7.4,2.6,2.6,4.2,3.4,9.2,3.4,9.2,8,11.2,4.6-6.6,4.6-6.6,0.2-1-2.6-4.6-2.6-5.8s-1.8-4.6-1.8-4.6c-3-3.4-0.6-10.4-0.6-10.4,1.8-13.8-0.4-12-0.4-12-1.2-1.8-10.4,8.2-10.4,8.2-2.2,3.4-8.2,5-8.2,5-2.799,1.8-6.199,0.4-6.199,0.4-2.6-0.4-8.2,6.6-8.2,6.6,2.8-0.2,5.2,4.2,7.6,4.4s4.2-2.4,5.799-3c1.6-0.6,4.4,5.2,4.4,5.2,0.4,2.6-5.2,7.4-5.2,7.4-0.4,4.6-1.999,3-1.999,3-3-0.6-4.2,3.2-5.2,7.8s-5.2,5-5.2,5c-1.6,7.4-2.801,4.4-2.801,4.4-0.2-5.6-6.2,0.2-6.2,0.2-1.2,2-5.8-0.2-5.8-0.2-6.8-2-4.4-4-4.4-4,1.8-2.2,13,0,13,0,2.2-1.6-5.8-5.6-5.8-5.6-0.6-1.8,0.4-6.2,0.4-6.2,1.2-3.2,8-8.8,8-8.8,9.401-1.2,6.601-2.8,6.601-2.8-6.2-5.2-12.001,2.4-12.001,2.4-2.2,6.2-19.6,21.2-19.6,21.2-4.8,3.4-2.2-3.4-6.2,0s-24.6-5.6-24.6-5.6c-11.562-1.193-14.294,14.549-17.823,11.429,0,0,5.418,8.52,3.818,2.92z"/> - </g> - <g id="g358" fill="#000"> - <path id="path360" d="m209.4-120s-25.6,8-28.4,26.8c0,0-2.4,22.8,18,40.4,0,0,0.4,6.4,2.4,9.6,0,0-1.6,4.8,17.2-2.8l27.2-8.4s6.4-2.4,11.6-11.2,20.4-27.6,16.8-52.8c0,0,1.2-11.2-4.8-11.6,0,0-8.4-1.6-15.6,6,0,0-6.8,3.2-9.2,2.8l-35.2,1.2z"/> - </g> - <g id="g362" fill="#000"> - <path id="path364" d="m264.02-120.99s2.1-8.93-2.74-4.09c0,0-7.04,5.72-14.52,5.72,0,0-14.52,2.2-18.92,15.4,0,0-3.96,26.84,3.96,32.56,0,0,4.84,7.48,11.88,0.88s22.54-36.83,20.34-50.47z"/> - </g> - <g id="g366" fill="#323232"> - <path id="path368" d="m263.65-120.63s2.09-8.75-2.66-3.99c0,0-6.92,5.61-14.26,5.61,0,0-14.26,2.16-18.58,15.12,0,0-3.89,26.354,3.89,31.97,0,0,4.75,7.344,11.66,0.864,6.92-6.48,22.11-36.184,19.95-49.574z"/> - </g> - <g id="g370" fill="#666"> - <path id="path372" d="m263.27-120.27s2.08-8.56-2.58-3.9c0,0-6.78,5.51-13.99,5.51,0,0-14,2.12-18.24,14.84,0,0-3.81,25.868,3.82,31.38,0,0,4.66,7.208,11.45,0.848,6.78-6.36,21.66-35.538,19.54-48.678z"/> - </g> - <g id="g374" fill="#999"> - <path id="path376" d="m262.9-119.92s2.07-8.37-2.51-3.79c0,0-6.65,5.41-13.73,5.41,0,0-13.72,2.08-17.88,14.56,0,0-3.75,25.372,3.74,30.78,0,0,4.58,7.072,11.23,0.832,6.66-6.24,21.23-34.892,19.15-47.792z"/> - </g> - <g id="g378" fill="#CCC"> - <path id="path380" d="m262.53-119.56s2.06-8.18-2.43-3.7c0,0-6.53,5.31-13.47,5.31,0,0-13.46,2.04-17.54,14.28,0,0-3.67,24.886,3.67,30.19,0,0,4.49,6.936,11.02,0.816,6.52-6.12,20.79-34.246,18.75-46.896z"/> - </g> - <g id="g382" fill="#FFF"> - <path id="path384" d="m262.15-119.2s2.05-8-2.35-3.6c0,0-6.4,5.2-13.2,5.2,0,0-13.2,2-17.2,14,0,0-3.6,24.4,3.6,29.6,0,0,4.4,6.8,10.8,0.8s20.35-33.6,18.35-46z"/> - </g> - <g id="g386" fill="#992600"> - <path id="path388" d="m50.6,84s-20.4-19.2-28.4-20c0,0-34.4-4-49.2,14,0,0,17.6-20.4,45.2-14.8,0,0-21.6-4.4-34-1.2l-26.4,14-2.8,4.8s4-14.8,22.4-20.8c0,0,22.8-4.8,33.6,0,0,0-21.6-6.8-31.6-4.8,0,0-30.4-2.4-43.2,24,0,0,4-14.4,18.8-21.6,0,0,13.6-8.8,34-6,0,0,14.4,3.2,19.6,5.6s4-0.4-4.4-5.2c0,0-5.6-10-19.6-9.6,0,0-42.8,3.6-53.2,15.6,0,0,13.6-11.2,24-14,0,0,22.4-8,30.8-7.2,0,0,24.8,1,32.4-3,0,0-11.2,5-8,8.2s10,10.8,10,12,24.2,23.3,27.8,27.7l2.2,2.3z"/> - </g> - <g id="g390" fill="#CCC"> - <path id="path392" d="m189,278s-15.5-36.5-28-46c0,0,26,16,29.5,34,0,0,0,10-1.5,12z"/> - </g> - <g id="g394" fill="#CCC"> - <path id="path396" d="m236,285.5s-26.5-55-45-79c0,0,43.5,37.5,48.5,64l0.5,5.5-3-2.5s-0.5,9-1,12z"/> - </g> - <g id="g398" fill="#CCC"> - <path id="path400" d="m292.5,237s-62.5-59.5-64-62c0,0,60.5,66,63.5,73.5,0,0-2-9,0.5-11.5z"/> - </g> - <g id="g402" fill="#CCC"> - <path id="path404" d="m104,280.5s19.5-52,38.5-29.5c0,0,15,10,14.5,13,0,0-4-6.5-22-6,0,0-19-3-31,22.5z"/> - </g> - <g id="g406" fill="#CCC"> - <path id="path408" d="m294.5,153s-45-28.5-52.5-30c-11.81-2.36,49.5,29,54.5,39.5,0,0,2-2.5-2-9.5z"/> - </g> - <g id="g410" fill="#000"> - <path id="path412" d="m143.8,259.6s20.4-2,27.2-8.8l4.4,3.6,17.6-38.4,3.6,5.2s14.4-14.8,13.6-22.8,12.8,6,12.8,6-0.8-11.6,6.4-4.8c0,0-2.4-15.6,6-7.6,0,0-10.54-30.16,12-4.4,5.6,6.4,1.2-0.4,1.2-0.4s-26-48-4.4-33.6c0,0,2-22.8,0.8-27.2s-3.2-26.8-8-32,0.4-6.8,6-1.6c0,0-11.2-24,2-12,0,0-3.6-15.2-8-18,0,0-5.6-17.2,9.6-6.4,0,0-4.4-12.4-7.6-15.6,0,0-11.6-27.6-4.4-22.8l4.4,3.6s-6.8-14-0.4-9.6,6.4,4,6.4,4-21.2-33.2-0.8-15.6c0,0-8.16-13.918-11.6-20.8,0,0-18.8-20.4-4.4-14l4.8,1.6s-8.8-10-16.8-11.6,2.4-8,8.8-6,22,9.6,22,9.6,12.8,18.8,16.8,19.2c0,0-20-7.6-14,0.4,0,0,14.4,14,7.2,13.6,0,0-6,7.2-1.2,16,0,0-18.46-18.391-3.6,7.2l6.8,16.4s-24.4-24.8-13.2-2.8c0,0,17.2,23.6,19.2,24s6.4,9.2,6.4,9.2l-4.4-2,5.2,8.8s-11.2-12-5.2,1.2l5.6,14.4s-20.4-22-6.8,7.6c0,0-16.4-5.2-7.6,12,0,0-1.6,16-1.2,21.2s1.6,33.6-2.8,41.6,6,27.2,8,31.2,5.6,14.8-3.2,5.6-4.4-3.6-2.4,5.2,8,24.4,7.2,30c0,0-1.2,1.2-4.4-2.4,0,0-14.8-22.8-13.2-8.4,0,0-1.2,8-4.4,16.8,0,0-3.2,10.8-3.2,2,0,0-3.2-16.8-6-9.2s-6.4,13.6-9.2,16-8-20.4-9.2-10c0,0-12-12.4-16.8,4l-11.6,16.4s-0.4-12.4-1.6-6.4c0,0-30,6-40.4,1.6z"/> - </g> - <g id="g414" fill="#000"> - <path id="path416" d="m109.4-97.2s-11.599-8-15.599-7.6,27.599-8.8,68.799,18.8c0,0,4.8,2.8,8.4,2.4,0,0,3.2,2.4,0.4,6,0,0-8.8,9.6,2.4,20.8,0,0,18.4,6.8,12.8-2,0,0,10.8,4,13.2,8s1.2,0,1.2,0l-12.4-12.4s-5.2-2-8-10.4-5.2-18.4-0.8-21.6c0,0-4,4.4-3.2,0.4s4.4-7.6,6-8,18-16.2,24.8-16.6c0,0-9.2,1.4-12.2,0.4s-29.6-12.4-35.6-13.6c0,0-16.8-6.6-4.8-4.6,0,0,35.8,3.8,54,17,0,0-7.2-8.4-25.6-15.4,0,0-22.2-12.6-57.4-7.6,0,0-17.8,3.2-25.6,5,0,0-2.599-0.6-3.199-1s-12.401-9.4-40.001-2.4c0,0-17,4.6-25.6,9.4,0,0-15.2,1.2-18.8,4.4,0,0-18.6,14.6-20.6,15.4s-13.4,8.4-14.2,8.8c0,0,24.6-6.6,27-9s19.8-5,22.2-3.6,10.8,0.8,1.2,1.4c0,0,75.6,14.8,76.4,16.8s4.8,0.8,4.8,0.8z"/> - </g> - <g id="g418" fill="#cc7226"> - <path id="path420" d="m180.8-106.4s-10.2-7.4-12.2-7.4-14.4-10.2-18.6-9.8-16.4-9.6-43.8-1.4c0,0-0.6-2,3-2.8,0,0,6.4-2.2,6.8-2.8,0,0,20.2-4.2,27.4-0.6,0,0,9.2,2.6,15.4,8.8,0,0,11.2,3.2,14.4,2.2,0,0,8.8,2.2,9.2,4,0,0,5.8,3,4,5.6,0,0,0.4,1.6-5.6,4.2z"/> - </g> - <g id="g422" fill="#cc7226"> - <path id="path424" d="m168.33-108.51c0.81,0.63,1.83,0.73,2.43,1.54,0.24,0.31-0.05,0.64-0.37,0.74-1.04,0.31-2.1-0.26-3.24,0.33-0.4,0.21-1.04,0.03-1.6-0.12-1.63-0.44-3.46-0.47-5.15,0.22-1.98-1.13-4.34-0.54-6.42-1.55-0.06-0.02-0.28,0.32-0.36,0.3-3.04-1.15-6.79-0.87-9.22-3.15-2.43-0.41-4.78-0.87-7.21-1.55-1.82-0.51-3.23-1.5-4.85-2.33-1.38-0.71-2.83-1.23-4.37-1.61-1.86-0.45-3.69-0.34-5.58-0.86-0.1-0.02-0.29,0.32-0.37,0.3-0.32-0.11-0.62-0.69-0.79-0.64-1.68,0.52-3.17-0.45-4.83-0.11-1.18-1.22-2.9-0.98-4.45-1.42-2.97-0.85-6.12,0.42-9.15-0.58,4.11-1.84,8.8-0.61,12.86-2.68,2.33-1.18,4.99-0.08,7.56-0.84,0.49-0.15,1.18-0.35,1.58,0.32,0.14-0.14,0.32-0.37,0.38-0.35,2.44,1.16,4.76,2.43,7.24,3.5,0.34,0.15,0.88-0.09,1.13,0.12,1.52,1.21,3.46,1.11,4.85,2.33,1.7-0.5,3.49-0.12,5.22-0.75,0.08-0.02,0.31,0.32,0.34,0.3,1.14-0.75,2.29-0.48,3.18-0.18,0.34,0.12,1,0.37,1.31,0.44,1.12,0.27,1.98,0.75,3.16,0.94,0.11,0.02,0.3-0.32,0.37-0.3,1.12,0.44,2.16,0.39,2.82,1.55,0.14-0.14,0.3-0.37,0.38-0.35,1.03,0.34,1.68,1.1,2.78,1.34,0.48,0.1,1.1,0.73,1.67,0.91,2.39,0.73,4.24,2.26,6.43,3.15,0.76,0.31,1.64,0.55,2.27,1.04z"/> - </g> - <g id="g426" fill="#cc7226"> - <path id="path428" d="m91.696-122.74c-2.518-1.72-4.886-2.83-7.328-4.62-0.181-0.13-0.541,0.04-0.743-0.08-1.007-0.61-1.895-1.19-2.877-1.89-0.539-0.38-1.36-0.37-1.868-0.63-2.544-1.29-5.173-1.85-7.68-3.04,0.682-0.64,1.804-0.39,2.4-1.2,0.195,0.28,0.433,0.56,0.786,0.37,1.678-0.9,3.528-1.05,5.204-0.96,1.704,0.09,3.424,0.39,5.199,0.67,0.307,0.04,0.506,0.56,0.829,0.66,2.228,0.66,4.617,0.14,6.736,0.98,1.591,0.63,3.161,1.45,4.4,2.72,0.252,0.26-0.073,0.57-0.353,0.76,0.388-0.11,0.661,0.1,0.772,0.41,0.084,0.24,0.084,0.54,0,0.78-0.112,0.31-0.391,0.41-0.765,0.46-1.407,0.19,0.365-1.19-0.335-0.74-1.273,0.82-0.527,2.22-1.272,3.49-0.28-0.19-0.51-0.41-0.4-0.8,0.234,0.52-0.368,0.81-0.536,1.13-0.385,0.72-1.284,2.14-2.169,1.53z"/> - </g> - <g id="g430" fill="#cc7226"> - <path id="path432" d="m59.198-115.39c-3.154-0.79-6.204-0.68-9.22-1.96-0.067-0.02-0.29,0.32-0.354,0.3-1.366-0.6-2.284-1.56-3.36-2.61-0.913-0.89-2.571-0.5-3.845-0.99-0.324-0.12-0.527-0.63-0.828-0.67-1.219-0.16-2.146-1.11-3.191-1.68,2.336-0.8,4.747-0.76,7.209-1.15,0.113-0.02,0.258,0.31,0.391,0.31,0.136,0,0.266-0.23,0.4-0.36,0.195,0.28,0.497,0.61,0.754,0.35,0.548-0.54,1.104-0.35,1.644-0.31,0.144,0.01,0.269,0.32,0.402,0.32,0.136,0,0.267-0.32,0.4-0.32,0.136,0,0.267,0.32,0.4,0.32,0.136,0,0.266-0.23,0.4-0.36,0.692,0.78,1.577,0.23,2.399,0.41,1.038,0.22,1.305,1.37,2.379,1.67,4.715,1.3,8.852,3.45,13.215,5.54,0.307,0.14,0.517,0.39,0.407,0.78,0.267,0,0.58-0.09,0.77,0.04,1.058,0.74,2.099,1.28,2.796,2.38,0.216,0.34-0.113,0.75-0.346,0.7-4.429-1-8.435-1.61-12.822-2.71z"/> - </g> - <g id="g434" fill="#cc7226"> - <path id="path436" d="m45.338-71.179c-1.592-1.219-2.176-3.25-3.304-5.042-0.214-0.34,0.06-0.654,0.377-0.743,0.56-0.159,1.103,0.319,1.512,0.521,1.745,0.862,3.28,2.104,5.277,2.243,1.99,2.234,6.25,2.619,6.257,6,0.001,0.859-1.427-0.059-1.857,0.8-2.451-1.003-4.84-0.9-7.22-2.367-0.617-0.381-0.287-0.834-1.042-1.412z"/> - </g> - <g id="g438" fill="#cc7226"> - <path id="path440" d="m17.8-123.76c0.135,0,7.166,0.24,7.149,0.35-0.045,0.31-7.775,1.36-8.139,1.19-0.164-0.08-7.676,2.35-7.81,2.22,0.268-0.14,8.534-3.76,8.8-3.76z"/> - </g> - <g id="g442" fill="#000"> - <path id="path444" d="m33.2-114s-14.8,1.8-19.2,3-23,8.8-26,10.8c0,0-13.4,5.4-30.4,25.4,0,0,7.6-3.4,9.8-6.2,0,0,13.6-12.6,13.4-10,0,0,12.2-8.6,11.6-6.4,0,0,24.4-11.2,22.4-8,0,0,21.6-4.6,20.6-2.6,0,0,18.8,4.4,16,4.6,0,0-5.8,1.2,0.6,4.8,0,0-3.4,4.4-8.8,0.4s-2.4-1.8-7.4-0.8c0,0-2.6,0.8-7.2-3.2,0,0-5.6-4.6-14.4-1,0,0-30.6,12.6-32.6,13.2,0,0-3.6,2.8-6,6.4,0,0-5.8,4.4-8.8,5.8,0,0-12.8,11.6-14,13,0,0-3.4,5.2-4.2,5.6,0,0,6.4-3.8,8.4-5.8,0,0,14-10,19.4-10.8,0,0,4.4-3,5.2-4.4,0,0,14.4-9.2,18.6-9.2,0,0,9.2,5.2,11.6-1.8,0,0,5.8-1.8,11.4-0.6,0,0,3.2-2.6,2.4-4.8,0,0,1.6-1.8,2.6,2,0,0,3.4,3.6,8.2,1.6,0,0,4-0.2,2,2.2,0,0-4.4,3.8-16.2,4,0,0-12.4,0.6-28.8,8.2,0,0-29.8,10.4-39,20.8,0,0-6.4,8.8-11.8,10,0,0-5.8,0.8-11.8,8.2,0,0,9.8-5.8,18.8-5.8,0,0,4-2.4,0.2,1.2,0,0-3.6,7.6-2,13,0,0-0.6,5.2-1.4,6.8,0,0-7.8,12.8-7.8,15.2s1.2,12.2,1.6,12.8-1-1.6,2.8,0.8,6.6,4,7.4,6.8-2-5.4-2.2-7.2-4.4-9-3.6-11.4c0,0,1,1,1.8,2.4,0,0-0.6-0.6,0-4.2,0,0,0.8-5.2,2.2-8.4s3.4-7,3.8-7.8,0.4-6.6,1.8-4l3.4,2.6s-2.8-2.6-0.6-4.8c0,0-1-5.6,0.8-8.2,0,0,7-8.4,8.6-9.4s0.2-0.6,0.2-0.6,6-4.2,0.2-2.6c0,0-4,1.6-7,1.6,0,0-7.6,2-3.6-2.2s14-9.6,17.8-9.4l0.8,1.6,11.2-2.4-1.2,0.8s-0.2-0.2,4-0.6,10,1,11.4-0.8,4.8-2.8,4.4-1.4-0.6,3.4-0.6,3.4,5-5.8,4.4-3.6-8.8,7.4-10.2,13.6l10.4-8.2,3.6-3s3.6,2.2,3.8,0.6,4.8-7.4,6-7.2,3.2-2.6,3,0,7.4,8,7.4,8,3.2-1.8,4.6-0.4,5.6-19.8,5.6-19.8l25-10.6,43.6-3.4-16.999-6.8-61.001-11.4z"/> - </g> - <g id="g446" stroke-width="2" stroke="#4c0000"> - <path id="path448" d="m51.4,85s-15-16.8-23.4-19.4c0,0-13.4-6.8-38,1"/> - </g> - <g id="g450" stroke-width="2" stroke="#4c0000"> - <path id="path452" d="m24.8,64.2s-25.2-8-40.6-3.8c0,0-18.4,2-26.8,15.8"/> - </g> - <g id="g454" stroke-width="2" stroke="#4c0000"> - <path id="path456" d="m21.2,63s-17-7.2-31.8-9.4c0,0-16.6-2.6-33.2,4.6,0,0-12.2,6-17.6,16.2"/> - </g> - <g id="g458" stroke-width="2" stroke="#4c0000"> - <path id="path460" d="m22.2,63.4s-15.4-11-16.4-12.4c0,0-7-11-20-11.4,0,0-21.4,0.8-38.6,8.8"/> - </g> - <g id="g462" fill="#000"> - <path id="path464" d="M20.895,54.407c1.542,1.463,28.505,30.393,28.505,30.393,35.2,36.6,7.2,2.4,7.2,2.4-7.6-4.8-16.8-23.6-16.8-23.6-1.2-2.8,14,7.2,14,7.2,4,0.8,17.6,20,17.6,20-6.8-2.4-2,4.8-2,4.8,2.8,2,23.201,17.6,23.201,17.6,3.6,4,7.599,5.6,7.599,5.6,14-5.2,7.6,8,7.6,8,2.4,6.8,8-4.8,8-4.8,11.2-16.8-5.2-14.4-5.2-14.4-30,2.8-36.8-13.2-36.8-13.2-2.4-2.4,6.4,0,6.4,0,8.401,2-7.2-12.4-7.2-12.4,2.4,0,11.6,6.8,11.6,6.8,10.401,9.2,12.401,7.2,12.401,7.2,17.999-8.8,28.399-1.2,28.399-1.2,2,1.6-3.6,8.4-2,13.6s6.4,17.6,6.4,17.6c-2.4,1.6-2,12.4-2,12.4,16.8,23.2,7.2,21.2,7.2,21.2-15.6-0.4-0.8,7.2-0.8,7.2,3.2,2,12,9.2,12,9.2-2.8-1.2-4.4,4-4.4,4,4.8,4,2,8.8,2,8.8-6,1.2-7.2,5.2-7.2,5.2,6.8,8-3.2,8.4-3.2,8.4,3.6,4.4-1.2,16.4-1.2,16.4-4.8,0-11.2,5.6-11.2,5.6,2.4,4.8-8,10.4-8,10.4-8.4,1.6-5.6,8.4-5.6,8.4-7.999,6-10.399,22-10.399,22-0.8,10.4-3.2,13.6,2,11.6,5.199-2,4.399-14.4,4.399-14.4-4.799-15.6,38-31.6,38-31.6,4-1.6,4.8-6.8,4.8-6.8,2,0.4,10.8,8,10.8,8,7.6,11.2,8,2,8,2,1.2-3.6-0.4-9.6-0.4-9.6,6-21.6-8-28-8-28-10-33.6,4-25.2,4-25.2,2.8,5.6,13.6,10.8,13.6,10.8l3.6-2.4c-1.6-4.8,6.8-10.8,6.8-10.8,2.8,6.4,8.8-1.6,8.8-1.6,3.6-24.4,16-10,16-10,4,1.2,5.2-5.6,5.2-5.6,3.6-10.4,0-24,0-24,3.6-0.4,13.2,5.6,13.2,5.6,2.8-3.6-6.4-20.4-2.4-18s8.4,4,8.4,4c0.8-2-9.2-14.4-9.2-14.4-4.4-2.8-9.6-23.2-9.6-23.2,7.2,3.6-2.8-11.6-2.8-11.6,0-3.2,6-14.4,6-14.4-0.8-6.8,0-6.4,0-6.4,2.8,1.2,10.8,2.8,4-3.6s0.8-11.2,0.8-11.2c4.4-2.8-9.2-2.4-9.2-2.4-5.2-4.4-4.8-8.4-4.8-8.4,8,2-6.4-12.4-8.8-16s7.2-8.8,7.2-8.8c13.2-3.6,1.6-6.8,1.6-6.8-19.6,0.4-8.8-10.4-8.8-10.4,6,0.4,4.4-2,4.4-2-5.2-1.2-14.8-7.6-14.8-7.6-4-3.6-0.4-2.8-0.4-2.8,16.8,1.2-12-10-12-10,8,0-10-10.4-10-10.4-2-1.6-5.2-9.2-5.2-9.2-6-5.2-10.8-12-10.8-12-0.4-4.4-5.2-9.2-5.2-9.2-11.6-13.6-17.2-13.2-17.2-13.2-14.8-3.6-20-2.8-20-2.8l-52.8,4.4c-26.4,12.8-18.6,33.8-18.6,33.8,6.4,8.4,15.6,4.6,15.6,4.6,4.6-6.2,16.2-4,16.2-4,20.401,3.2,17.801-0.4,17.801-0.4-2.4-4.6-18.601-10.8-18.801-11.4s-9-4-9-4c-3-1.2-7.4-10.4-7.4-10.4-3.2-3.4,12.6,2.4,12.6,2.4-1.2,1,6.2,5,6.2,5,17.401-1,28.001,9.8,28.001,9.8,10.799,16.6,10.999,8.4,10.999,8.4,2.8-9.4-9-30.6-9-30.6,0.4-2,8.6,4.6,8.6,4.6,1.4-2,2.2,3.8,2.2,3.8,0.2,2.4,4,10.4,4,10.4,2.8,13,6.4,5.6,6.4,5.6l4.6,9.4c1.4,2.6-4.6,10.2-4.6,10.2-0.2,2.8,0.6,2.6-5,10.2s-2.2,12-2.2,12c-1.4,6.6,7.4,6.2,7.4,6.2,2.6,2.2,6,2.2,6,2.2,1.8,2,4.2,1.4,4.2,1.4,1.6-3.8,7.8-1.8,7.8-1.8,1.4-2.4,9.6-2.8,9.6-2.8,1-2.6,1.4-4.2,4.8-4.8s-21.2-43.6-21.2-43.6c6.4-0.8-1.8-13.2-1.8-13.2-2.2-6.6,9.2,8,11.4,9.4s3.2,3.6,1.6,3.4-3.4,2-2,2.2,14.4,15.2,17.8,25.4,9.4,14.2,15.6,20.2,5.4,30.2,5.4,30.2c-0.4,8.8,5.6,19.4,5.6,19.4,2,3.8-2.2,22-2.2,22-2,2.2-0.6,3-0.6,3,1,1.2,7.8,14.4,7.8,14.4-1.8-0.2,1.8,3.4,1.8,3.4,5.2,6-1.2,3-1.2,3-6-1.6,1,8.2,1,8.2,1.2,1.8-7.8-2.8-7.8-2.8-9.2-0.6,2.4,6.6,2.4,6.6,8.6,7.2-2.8,2.8-2.8,2.8-4.6-1.8-1.4,5-1.4,5,3.2,1.6,20.4,8.6,20.4,8.6,0.4,3.8-2.6,8.8-2.6,8.8,0.4,4-1.8,7.4-1.8,7.4-1.2,8.2-1.8,9-1.8,9-4.2,0.2-11.6,14-11.6,14-1.8,2.6-12,14.6-12,14.6-2,7-20-0.2-20-0.2-6.6,3.4-4.6,0-4.6,0-0.4-2.2,4.4-8.2,4.4-8.2,7-2.6,4.4-13.4,4.4-13.4,4-1.4-7.2-4.2-7-5.4s6-2.6,6-2.6c8-2,3.6-4.4,3.6-4.4-0.6-4,2.4-9.6,2.4-9.6,11.6-0.8,0-17,0-17-10.8-7.6-11.8-13.4-11.8-13.4,12.6-8.2,4.4-20.6,4.6-24.2s1.4-25.2,1.4-25.2c-2-6.2-5-19.8-5-19.8,2.2-5.2,9.6-17.8,9.6-17.8,2.8-4.2,11.6-9,9.4-12s-10-1.2-10-1.2c-7.8-1.4-7.2,3.8-7.2,3.8-1.6,1-2.4,6-2.4,6-0.72,7.933-9.6,14.2-9.6,14.2-11.2,6.2-2,10.2-2,10.2,6,6.6-3.8,6.8-3.8,6.8-11-1.8-2.8,8.4-2.8,8.4,10.8,12.8,7.8,15.6,7.8,15.6-10.2,1,2.4,10.2,2.4,10.2s-0.8-2-0.6-0.2,3.2,6,4,8-3.2,2.2-3.2,2.2c0.6,9.6-14.8,5.4-14.8,5.4l-1.6,0.2c-1.6,0.2-12.8-0.6-18.6-2.8s-12.599-2.2-12.599-2.2-4,1.8-11.601,1.6c-7.6-0.2-15.6,2.6-15.6,2.6-4.4-0.4,4.2-4.8,4.4-4.6s5.8-5.4-2.2-4.8c-21.797,1.635-32.6-8.6-32.6-8.6-2-1.4-4.6-4.2-4.6-4.2-10-2,1.4,12.4,1.4,12.4,1.2,1.4-0.2,2.4-0.2,2.4-0.8-1.6-8.6-7-8.6-7-2.811-0.973-4.174-2.307-6.505-4.793z"/> - </g> - <g id="g466" fill="#4c0000"> - <path id="path468" d="m-3,42.8s11.6,5.6,14.2,8.4,16.6,14.2,16.6,14.2-5.4-2-8-3.8-13.4-10-13.4-10-3.8-6-9.4-8.8z"/> - </g> - <g id="g470" fill="#99cc32"> - <path id="path472" d="M-61.009,11.603c0.337-0.148-0.187-2.86-0.391-3.403-1.022-2.726-10-4.2-10-4.2-0.227,1.365-0.282,2.961-0.176,4.599,0,0,4.868,5.519,10.567,3.004z"/> - </g> - <g id="g474" fill="#659900"> - <path id="path476" d="M-61.009,11.403c-0.449,0.158-0.015-2.734-0.191-3.203-1.022-2.726-10.2-4.3-10.2-4.3-0.227,1.365-0.282,2.961-0.176,4.599,0,0,4.268,5.119,10.567,2.904z"/> - </g> - <g id="g478" fill="#000"> - <path id="path480" d="m-65.4,11.546c-0.625,0-1.131-1.14-1.131-2.546,0-1.405,0.506-2.545,1.131-2.545s1.132,1.14,1.132,2.545c0,1.406-0.507,2.546-1.132,2.546z"/> - </g> - <g id="g482" fill="#000"> - <path id="path484" d="M-65.4,9z"/> - </g> - <g id="g486" fill="#000"> - <path id="path488" d="m-111,109.6s-5.6,10,19.2,4c0,0,14-1.2,16.4-3.6,1.2,0.8,9.566,3.73,12.4,4.4,6.8,1.6,15.2-8.4,15.2-8.4s4.6-10.5,7.4-10.5-0.4,1.6-0.4,1.6-6.6,10.1-6.2,11.7c0,0-5.2,20-21.2,20.8,0,0-16.15,0.95-14.8,6.8,0,0,8.8-2.4,11.2,0,0,0,10.8-0.4,2.8,6l-6.8,11.6s0.14,3.92-10,0.4c-9.8-3.4-20.1-16.3-20.1-16.3s-15.95-14.55-5.1-28.5z"/> - </g> - <g id="g490" fill="#e59999"> - <path id="path492" d="m-112.2,113.6s-2,9.6,34.8-0.8l6.8,0.8c2.4,0.8,14.4,3.6,16.4,2.4,0,0-7.2,13.6-18.8,12,0,0-13.2,1.6-12.8,6.4,0,0,4,7.2,8.8,9.6,0,0,2.8,2.4,2.4,5.6s-3.2,4.8-5.2,5.6-5.2-2.4-6.8-2.4-10-6.4-14.4-11.2-12.8-16.8-12.4-19.6,1.2-8.4,1.2-8.4z"/> - </g> - <g id="g494" fill="#b26565"> - <path id="path496" d="m-109,131.05c2.6,3.95,5.8,8.15,8,10.55,4.4,4.8,12.8,11.2,14.4,11.2s4.8,3.2,6.8,2.4,4.8-2.4,5.2-5.6-2.4-5.6-2.4-5.6c-3.066-1.53-5.806-5.02-7.385-7.35,0,0,0.185,2.55-5.015,1.75s-10.4-3.6-12-6.8-4-5.6-2.4-2,4,7.2,5.6,7.6,1.2,1.6-1.2,1.2-5.2-0.8-9.6-6z"/> - </g> - <g id="g498" fill="#992600"> - <path id="path500" d="m-111.6,110s1.8-13.6,3-17.6c0,0-0.8-6.8,1.6-11s4.4-10.4,7.4-15.8,3.2-9.4,7.2-11,10-10.2,12.8-11.2,2.6-0.2,2.6-0.2,6.8-14.8,20.4-10.8c0,0-16.2-2.8-0.4-12.2,0,0-4.8,1.1-1.5-5.9,2.201-4.668,1.7,2.1-9.3,13.9,0,0-5,8.6-10.2,11.6s-17.2,10-18.4,13.8-4.4,9.6-6.4,11.2-4.8,5.8-5.2,9.2c0,0-1.2,4-2.6,5.2s-1.6,4.4-1.6,6.4-2,4.8-1.8,7.2c0,0,0.8,19,0.4,21l2-3.8z"/> - </g> - <g id="g502" fill="#FFF"> - <path id="path504" d="m-120.2,114.6s-2-1.4-6.4,4.6c0,0,7.3,33,7.3,34.4,0,0,1.1-2.1-0.2-9.3s-2.2-19.9-2.2-19.9l1.5-9.8z"/> - </g> - <g id="g506" fill="#992600"> - <path id="path508" d="m-98.6,54s-17.6,3.2-17.2,32.4l-0.8,24.8s-1.2-25.6-2.4-27.2,2.8-12.8-0.4-6.8c0,0-14,14-6,35.2,0,0,1.5,3.3-1.5-1.3,0,0-4.6-12.6-3.5-19,0,0,0.2-2.2,2.1-5,0,0,8.6-11.7,11.3-14,0,0,1.8-14.4,17.2-19.6,0,0,5.7-2.3,1.2,0.5z"/> - </g> - <g id="g510" fill="#000"> - <path id="path512" d="m40.8-12.2c0.66-0.354,0.651-1.324,1.231-1.497,1.149-0.344,1.313-1.411,1.831-2.195,0.873-1.319,1.066-2.852,1.648-4.343,0.272-0.7,0.299-1.655-0.014-2.315-1.174-2.481-1.876-4.93-3.318-7.356-0.268-0.45-0.53-1.244-0.731-1.842-0.463-1.384-1.72-2.375-2.58-3.695-0.288-0.441,0.237-1.366-0.479-1.45-0.897-0.105-2.346-0.685-2.579,0.341-0.588,2.587,0.423,5.11,1.391,7.552-0.782,0.692-0.448,1.613-0.296,2.38,0.71,3.606-0.488,6.958-1.249,10.432-0.023,0.104,0.319,0.302,0.291,0.364-1.222,2.686-2.674,5.131-4.493,7.512-0.758,0.992-1.63,1.908-2.127,2.971-0.368,0.787-0.776,1.753-0.526,2.741-3.435,2.78-5.685,6.625-8.296,10.471-0.462,0.68-0.171,1.889,0.38,2.158,0.813,0.398,1.769-0.626,2.239-1.472,0.389-0.698,0.742-1.348,1.233-1.991,0.133-0.175-0.046-0.594,0.089-0.715,2.633-2.347,4.302-5.283,6.755-7.651,1.95-0.329,3.487-1.327,5.235-2.34,0.308-0.179,0.832,0.07,1.122-0.125,1.753-1.177,1.751-3.213,1.857-5.123,0.05-0.884,0.246-2.201,1.386-2.812z"/> - </g> - <g id="g514" fill="#000"> - <path id="path516" d="m31.959-16.666c0.124-0.077-0.031-0.5,0.078-0.716,0.162-0.324,0.565-0.512,0.727-0.836,0.109-0.216-0.054-0.596,0.082-0.738,2.333-2.447,2.59-5.471,1.554-8.444,1.024-0.62,1.085-1.882,0.66-2.729-0.853-1.7-1.046-3.626-2.021-5.169-0.802-1.269-2.38-2.513-3.751-1.21-0.421,0.4-0.742,1.187-0.464,1.899,0.064,0.163,0.349,0.309,0.322,0.391-0.107,0.324-0.653,0.548-0.659,0.82-0.03,1.496-0.984,3.007-0.354,4.336,0.772,1.629,1.591,3.486,2.267,5.262-1.234,2.116-0.201,4.565-1.954,6.442-0.136,0.146-0.127,0.532-0.005,0.734,0.292,0.486,0.698,0.892,1.184,1.184,0.202,0.121,0.55,0.123,0.75-0.001,0.578-0.362,0.976-0.849,1.584-1.225z"/> - </g> - <g id="g518" fill="#000"> - <path id="path520" d="m94.771-26.977c1.389,1.792,1.679,4.587-0.37,5.977,0.55,3.309,3.901,1.33,5.999,0.8-0.11-0.388,0.12-0.732,0.4-0.737,1.06-0.015,1.74-1.047,2.8-0.863,0.44-1.557,2.07-2.259,2.72-3.639,1.72-3.695,1.13-7.968-1.45-11.214-0.2-0.254,0.01-0.771-0.11-1.133-0.76-2.211-2.82-2.526-4.76-3.214-1.176-3.875-1.837-7.906-3.599-11.6-1.614-0.25-2.312-1.989-3.649-2.709-1.333-0.719-1.901,0.86-1.86,1.906,0.007,0.205,0.459,0.429,0.289,0.794-0.076,0.164-0.336,0.275-0.336,0.409,0.001,0.135,0.222,0.266,0.356,0.4-0.918,0.82-2.341,1.297-2.636,2.442-0.954,3.71,1.619,6.835,3.287,10.036,0.591,1.135-0.145,2.406-0.905,3.614-0.438,0.695-0.33,1.822-0.054,2.678,0.752,2.331,2.343,4.07,3.878,6.053z"/> - </g> - <g id="g522" fill="#000"> - <path id="path524" d="m57.611-8.591c-1.487,1.851-4.899,4.42-1.982,6.348,0.194,0.129,0.564,0.133,0.737-0.001,2.021-1.565,4.024-2.468,6.46-3.05,0.124-0.029,0.398,0.438,0.767,0.277,1.613-0.703,3.623-0.645,4.807-1.983,3.767,0.224,7.332-0.892,10.723-2.2,1.161-0.448,2.431-1.007,3.632-1.509,1.376-0.576,2.58-1.504,3.692-2.645,0.133-0.136,0.487-0.046,0.754-0.046-0.04-0.863,0.922-0.99,1.169-1.612,0.092-0.232-0.058-0.628,0.075-0.73,2.138-1.63,3.058-3.648,1.889-6.025-0.285-0.578-0.534-1.196-1.1-1.672-1.085-0.911-2.187-0.057-3.234-0.361-0.159,0.628-0.888,0.456-1.274,0.654-0.859,0.439-2.192-0.146-3.051,0.292-1.362,0.695-2.603,0.864-4.025,1.241-0.312,0.082-1.09-0.014-1.25,0.613-0.134-0.134-0.282-0.368-0.388-0.346-1.908,0.396-3.168,0.61-4.469,2.302-0.103,0.133-0.545-0.046-0.704,0.089-0.957,0.808-1.362,2.042-2.463,2.714-0.201,0.123-0.553-0.045-0.747,0.084-0.646,0.431-1.013,1.072-1.655,1.519-0.329,0.229-0.729-0.096-0.697-0.352,0.245-1.947,0.898-3.734,0.323-5.61,2.077-2.52,4.594-4.469,6.4-7.2,0.015-2.166,0.707-4.312,0.594-6.389-0.01-0.193-0.298-0.926-0.424-1.273-0.312-0.854,0.594-1.92-0.25-2.644-1.404-1.203-2.696-0.327-3.52,1.106-1.838,0.39-3.904,1.083-5.482-0.151-1.007-0.787-1.585-1.693-2.384-2.749-0.985-1.302-0.65-2.738-0.58-4.302,0.006-0.128-0.309-0.264-0.309-0.398,0.001-0.135,0.221-0.266,0.355-0.4-0.706-0.626-0.981-1.684-2-2,0.305-1.092-0.371-1.976-1.242-2.278-1.995-0.691-3.672,1.221-5.564,1.294-0.514,0.019-0.981-1.019-1.63-1.344-0.432-0.216-1.136-0.249-1.498,0.017-0.688,0.504-1.277,0.618-2.035,0.823-1.617,0.436-2.895,1.53-4.375,2.385-1.485,0.857-2.44,2.294-3.52,3.614-0.941,1.152-1.077,3.566,0.343,4.066,1.843,0.65,3.147-2.053,5.113-1.727,0.312,0.051,0.518,0.362,0.408,0.75,0.389,0.109,0.607-0.12,0.8-0.4,0.858,1.019,2.022,1.356,2.96,2.229,0.97,0.904,2.716,0.486,3.731,1.483,1.529,1.502,0.97,4.183,2.909,5.488-0.586,1.313-1.193,2.59-1.528,4.017-0.282,1.206,0.712,2.403,1.923,2.312,1.258-0.094,1.52-0.853,2.005-1.929,0.267,0.267,0.736,0.564,0.695,0.78-0.457,2.387-1.484,4.38-1.942,6.811-0.059,0.317-0.364,0.519-0.753,0.409-0.468,4.149-4.52,6.543-7.065,9.708-0.403,0.502-0.407,1.751,0.002,2.154,1.403,1.387,3.363-0.159,5.063-0.662,0.213-1.206,1.072-2.148,2.404-2.092,0.256,0.01,0.491-0.532,0.815-0.662,0.348-0.138,0.85,0.086,1.136-0.112,1.729-1.195,3.137-2.301,4.875-3.49,0.192-0.131,0.536,0.028,0.752-0.08,0.325-0.162,0.512-0.549,0.835-0.734,0.348-0.2,0.59,0.09,0.783,0.37-0.646,0.349-0.65,1.306-1.232,1.508-0.775,0.268-1.336,0.781-2.01,1.228-0.292,0.193-0.951-0.055-1.055,0.124-0.598,1.028-1.782,1.466-2.492,2.349z"/> - </g> - <g id="g526" fill="#000"> - <path id="path528" d="m2.2-58s-9.238-2.872-20.4,22.8c0,0-2.4,5.2-4.8,7.2s-13.6,5.6-15.6,9.6l-10.4,16s14.8-16,18-18.4c0,0,8-8.4,4.8-1.6,0,0-14,10.8-12.8,20,0,0-5.6,14.4-6.4,16.4,0,0,16-32,18.4-33.2s3.6-1.2,2.4,2.4-1.6,20-4.4,22c0,0,8-20.4,7.2-23.6,0,0,3.2-3.6,5.6,1.6l-1.2,16,4.4,12s-2.4-11.2-0.8-26.8c0,0-2-10.4,2-4.8s13.6,11.6,13.6,16.4c0,0-5.2-17.6-14.4-22.4l-4,6-1.2-2s-3.6-0.8,0.8-7.6,4-7.6,4-7.6,6.4,7.2,8,7.2c0,0,13.2-7.6,14.4,16.8,0,0,6.8-14.4-2.4-21.2,0,0-14.8-2-13.6-7.2l7.2-12.4c3.6-5.2,2-2.4,2-2.4z"/> - </g> - <g id="g530" fill="#000"> - <path id="path532" d="m-17.8-41.6-16,5.2-7.2,9.6s17.2-10,21.2-11.2,2-3.6,2-3.6z"/> - </g> - <g id="g534" fill="#000"> - <path id="path536" d="m-57.8-35.2s-2,1.2-2.4,4-2.8,3.2-2,6,2.8,5.2,2.8,1.2,1.6-6,2.4-7.2,2.4-5.6-0.8-4z"/> - </g> - <g id="g538" fill="#000"> - <path id="path540" d="m-66.6,26s-8.4-4-11.6-7.6-2.748,1.566-7.6,1.2c-5.847-0.441-4.8-16.4-4.8-16.4l-4,7.6s-1.2,14.4,6.8,12c3.907-1.172,5.2,0.4,3.6,1.2s5.6,1.2,2.8,2.8,11.6-3.6,9.2,6.8l5.6-7.6z"/> - </g> - <g id="g542" fill="#000"> - <path id="path544" d="m-79.2,40.4s-15.4,4.4-19-5.2c0,0-4.8,2.4-2.6,5.4s3.4,3.4,3.4,3.4,5.4,1.2,4.8,2-3,4.2-3,4.2,10.2-6,16.4-9.8z"/> - </g> - <g id="g546" fill="#FFF"> - <path id="path548" d="m149.2,118.6c-0.43,2.14-2.1,2.94-4,3.6-1.92-0.96-4.51-4.06-6.4-2-0.47-0.48-1.25-0.54-1.6-1.2-0.46-0.9-0.19-1.94-0.53-2.74-0.55-1.28-1.25-2.64-1.07-4.06,1.81-0.71,2.4-2.62,1.93-4.38-0.07-0.26-0.5-0.45-0.3-0.8,0.19-0.33,0.5-0.55,0.77-0.82-0.13,0.14-0.28,0.37-0.39,0.35-0.61-0.11-0.49-0.75-0.36-1.13,0.59-1.75,2.6-2.01,3.95-0.82,0.26-0.56,0.77-0.37,1.2-0.4-0.05-0.58,0.36-1.11,0.56-1.53,0.52-1.09,2.14,0.01,2.94-0.6,1.08-0.83,2.14-1.52,3.22-0.92,1.81,1.01,3.52,2.22,4.72,3.97,0.57,0.83,0.81,2.11,0.75,3.07-0.04,0.65-1.42,0.29-1.76,1.22-0.65,1.75,1.19,2.27,1.94,3.61,0.2,0.35-0.06,0.65-0.38,0.75-0.41,0.13-1.19-0.06-1.06,0.39,0.98,3.19-1.78,3.87-4.13,4.44z"/> - </g> - <g id="g550" fill="#FFF"> - <path id="path552" d="m139.6,138.2c-0.01-1.74-1.61-3.49-0.4-5.2,0.14,0.14,0.27,0.36,0.4,0.36,0.14,0,0.27-0.22,0.4-0.36,1.5,2.22,5.15,3.14,5.01,5.99-0.03,0.45-1.11,1.37-0.21,2.01-1.81,1.35-1.87,3.72-2.8,5.6-1.24-0.28-2.45-0.65-3.6-1.2,0.35-1.48,0.24-3.17,1.06-4.49,0.43-0.7,0.14-1.78,0.14-2.71z"/> - </g> - <g id="g554" fill="#CCC"> - <path id="path556" d="m-26.6,129.2s-16.858,10.14-2.8-5.2c8.8-9.6,18.8-15.2,18.8-15.2s10.4-4.4,14-5.6,18.8-6.4,22-6.8,12.8-4.4,19.6-0.4,14.8,8.4,14.8,8.4-16.4-8.4-20-6-10.8,2-16.8,5.2c0,0-14.8,4.4-18,6.4s-13.6,13.6-15.2,12.8,0.4-1.2,1.6-4-0.8-4.4-8.8,2-9.2,8.4-9.2,8.4z"/> - </g> - <g id="g558" fill="#000"> - <path id="path560" d="m-19.195,123.23s1.41-13.04,9.888-11.37c0,0,8.226-4.17,10.948-6.14,0,0,8.139-1.7,9.449-2.32,18.479-8.698,33.198-4.179,33.745-5.299,0.546-1.119,20.171,5.999,23.78,10.079,0.391,0.45-10.231-5.59-19.929-7.48-8.273-1.617-29.875,0.24-40.781,5.78-2.973,1.51-11.918,7.29-14.449,7.18s-12.651,9.57-12.651,9.57z"/> - </g> - <g id="g562" fill="#CCC"> - <path id="path564" d="m-23,148.8s-15.2-2.4,1.6-4c0,0,18-2,22-7.2,0,0,13.6-9.2,16.4-9.6s32.8-7.6,33.2-10,6-2.4,7.6-1.6,0.8,2-2,2.8-34,17.2-40.4,18.4-18,8.8-22.8,10-15.6,1.2-15.6,1.2z"/> - </g> - <g id="g566" fill="#000"> - <path id="path568" d="m-3.48,141.4s-8.582-0.83,0.019-1.64c0,0,8.816-3.43,10.864-6.09,0,0,6.964-4.71,8.397-4.92,1.434-0.2,15.394-3.89,15.599-5.12s34.271-13.81,38.691-10.62c2.911,2.1-6.99,0.43-16.624,4.84-1.355,0.62-35.208,15.2-38.485,15.82-3.277,0.61-9.216,4.5-11.674,5.12-2.457,0.61-6.787,2.61-6.787,2.61z"/> - </g> - <g id="g570" fill="#000"> - <path id="path572" d="m-11.4,143.6s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/> - </g> - <g id="g574" fill="#000"> - <path id="path576" d="m-18.6,145.2s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/> - </g> - <g id="g578" fill="#000"> - <path id="path580" d="m-29,146.8s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/> - </g> - <g id="g582" fill="#000"> - <path id="path584" d="m-36.6,147.6s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/> - </g> - <g id="g586" fill="#000"> - <path id="path588" d="m1.8,108,3.2,1.6c-1.2,1.6-4.4,1.2-4.4,1.2l1.2-2.8z"/> - </g> - <g id="g590" fill="#000"> - <path id="path592" d="m-8.2,113.6s6.506-2.14,4,1.2c-1.2,1.6-3.6,0.8-3.6,0.8l-0.4-2z"/> - </g> - <g id="g594" fill="#000"> - <path id="path596" d="m-19.4,118.4s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/> - </g> - <g id="g598" fill="#000"> - <path id="path600" d="m-27,124.4s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/> - </g> - <g id="g602" fill="#000"> - <path id="path604" d="m-33.8,129.2s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/> - </g> - <g id="g606" fill="#000"> - <path id="path608" d="m5.282,135.6s6.921-0.53,5.324,1.6c-1.597,2.12-4.792,1.06-4.792,1.06l-0.532-2.66z"/> - </g> - <g id="g610" fill="#000"> - <path id="path612" d="m15.682,130.8s6.921-0.53,5.324,1.6c-1.597,2.12-4.792,1.06-4.792,1.06l-0.532-2.66z"/> - </g> - <g id="g614" fill="#000"> - <path id="path616" d="m26.482,126.4s6.921-0.53,5.324,1.6c-1.597,2.12-4.792,1.06-4.792,1.06l-0.532-2.66z"/> - </g> - <g id="g618" fill="#000"> - <path id="path620" d="m36.882,121.6s6.921-0.53,5.324,1.6c-1.597,2.12-4.792,1.06-4.792,1.06l-0.532-2.66z"/> - </g> - <g id="g622" fill="#000"> - <path id="path624" d="m9.282,103.6s6.921-0.53,5.324,1.6c-1.597,2.12-5.592,1.86-5.592,1.86l0.268-3.46z"/> - </g> - <g id="g626" fill="#000"> - <path id="path628" d="m19.282,100.4s6.921-0.534,5.324,1.6c-1.597,2.12-5.992,1.86-5.992,1.86l0.668-3.46z"/> - </g> - <g id="g630" fill="#000"> - <path id="path632" d="m-3.4,140.4s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/> - </g> - <g id="g634" fill="#992600"> - <path id="path636" d="m-76.6,41.2s-4.4,8.8-4.8,12c0,0,0.8-8.8,2-10.8s2.8-1.2,2.8-1.2z"/> - </g> - <g id="g638" fill="#992600"> - <path id="path640" d="m-95,55.2s-3.2,14.4-2.8,17.2c0,0-1.2-11.6-0.8-12.8s3.6-4.4,3.6-4.4z"/> - </g> - <g id="g642" fill="#CCC"> - <path id="path644" d="m-74.2-19.4-0.2,3.2-2.2,0.2s14.2,12.6,14.8,20.2c0,0,0.8-8.2-12.4-23.6z"/> - </g> - <g id="g646" fill="#000"> - <path id="path648" d="m-70.216-18.135c-0.431-0.416-0.212-1.161-0.62-1.421-0.809-0.516,1.298-0.573,1.07-1.289-0.383-1.206-0.196-1.227-0.318-2.503-0.057-0.598,0.531-2.138,0.916-2.578,1.446-1.652,0.122-4.584,1.762-6.135,0.304-0.289,0.68-0.841,0.965-1.259,0.659-0.963,1.843-1.451,2.793-2.279,0.318-0.276,0.117-1.103,0.686-1.011,0.714,0.115,1.955-0.015,1.91,0.826-0.113,2.12-1.442,3.84-2.722,5.508,0.451,0.704-0.007,1.339-0.291,1.896-1.335,2.62-1.146,5.461-1.32,8.301-0.005,0.085-0.312,0.163-0.304,0.216,0.353,2.335,0.937,4.534,1.816,6.763,0.366,0.93,0.837,1.825,0.987,2.752,0.111,0.686,0.214,1.519-0.194,2.224,2.035,2.89,0.726,5.541,1.895,9.072,0.207,0.625,1.899,2.539,1.436,2.378-2.513-0.871-2.625-1.269-2.802-2.022-0.146-0.623-0.476-2-0.713-2.602-0.064-0.164-0.235-2.048-0.313-2.17-1.513-2.382-0.155-2.206-1.525-4.564-1.428-0.68-2.394-1.784-3.517-2.946-0.198-0.204,0.945-0.928,0.764-1.141-1.092-1.289-2.245-2.056-1.909-3.549,0.155-0.69,0.292-1.747-0.452-2.467z"/> - </g> - <g id="g650" fill="#000"> - <path id="path652" d="m-73.8-16.4s0.4,6.8,2.8,8.4,1.2,0.8-2-0.4-2-2-2-2-2.8,0.4-0.4,2.4,6,4.4,4.4,4.4-9.2-4-9.2-6.8-1-6.9-1-6.9,1.1-0.8,5.9-0.7c0,0,1.4,0.7,1.5,1.6z"/> - </g> - <g id="g654" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path656" d="m-74.6,2.2s-8.52-2.791-27,0.6c0,0,9.031-2.078,27.8,0.2,10.3,1.25-0.8-0.8-0.8-0.8z"/> - </g> - <g id="g658" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path660" d="m-72.502,2.129s-8.246-3.518-26.951-1.737c0,0,9.178-1.289,27.679,2.603,10.154,2.136-0.728-0.866-0.728-0.866z"/> - </g> - <g id="g662" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path664" d="m-70.714,2.222s-7.962-4.121-26.747-3.736c0,0,9.248-0.604,27.409,4.654,9.966,2.885-0.662-0.918-0.662-0.918z"/> - </g> - <g id="g666" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path668" d="m-69.444,2.445s-6.824-4.307-23.698-5.405c0,0,8.339,0.17,24.22,6.279,8.716,3.353-0.522-0.874-0.522-0.874z"/> - </g> - <g id="g670" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path672" d="m45.84,12.961s-0.93,0.644-0.716-0.537c0.215-1.181,28.423-14.351,32.037-14.101,0,0-30.248,13.206-31.321,14.638z"/> - </g> - <g id="g674" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path676" d="m42.446,13.6s-0.876,0.715-0.755-0.479,27.208-16.539,30.83-16.573c0,0-29.117,15.541-30.075,17.052z"/> - </g> - <g id="g678" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path680" d="m39.16,14.975s-0.828,0.772-0.786-0.428c0.042-1.199,19.859-16.696,29.671-18.57,0,0-18.03,8.127-28.885,18.998z"/> - </g> - <g id="g682" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path684" d="m36.284,16.838s-0.745,0.694-0.707-0.385c0.038-1.08,17.872-15.027,26.703-16.713,0,0-16.226,7.314-25.996,17.098z"/> - </g> - <g id="g686" fill="#CCC"> - <path id="path688" d="m4.6,164.8s-15.2-2.4,1.6-4c0,0,18-2,22-7.2,0,0,13.6-9.2,16.4-9.6s19.2-4,19.6-6.4,6.4-4.8,8-4,1.6,10-1.2,10.8-21.6,8-28,9.2-18,8.8-22.8,10-15.6,1.2-15.6,1.2z"/> - </g> - <g id="g690" fill="#000"> - <path id="path692" d="m77.6,127.4s-3,1.6-4.2,4.2c0,0-6.4,10.6-20.6,13.8,0,0-23,9-30.8,11,0,0-13.4,5-20.8,4.2,0,0-7,0.2-0.8,1.8,0,0,20.2-2,23.6-3.8,0,0,15.6-5.2,18.6-7.8s21.2-7.6,23.4-9.6,12-10.4,11.6-13.8z"/> - </g> - <g id="g694" fill="#000"> - <path id="path696" d="m18.882,158.91s5.229-0.23,4.076,1.32-3.601,0.68-3.601,0.68l-0.475-2z"/> - </g> - <g id="g698" fill="#000"> - <path id="path700" d="m11.68,160.26s5.228-0.22,4.076,1.33c-1.153,1.55-3.601,0.67-3.601,0.67l-0.475-2z"/> - </g> - <g id="g702" fill="#000"> - <path id="path704" d="m1.251,161.51s5.229-0.23,4.076,1.32-3.601,0.68-3.601,0.68l-0.475-2z"/> - </g> - <g id="g706" fill="#000"> - <path id="path708" d="m-6.383,162.06s5.229-0.23,4.076,1.32-3.601,0.67-3.601,0.67l-0.475-1.99z"/> - </g> - <g id="g710" fill="#000"> - <path id="path712" d="m35.415,151.51s6.96-0.3,5.425,1.76c-1.534,2.07-4.793,0.9-4.793,0.9l-0.632-2.66z"/> - </g> - <g id="g714" fill="#000"> - <path id="path716" d="m45.73,147.09s5.959-3.3,5.425,1.76c-0.27,2.55-4.793,0.9-4.793,0.9l-0.632-2.66z"/> - </g> - <g id="g718" fill="#000"> - <path id="path720" d="m54.862,144.27s7.159-3.7,5.425,1.77c-0.778,2.44-4.794,0.9-4.794,0.9l-0.631-2.67z"/> - </g> - <g id="g722" fill="#000"> - <path id="path724" d="m64.376,139.45s4.359-4.9,5.425,1.76c0.406,2.54-4.793,0.9-4.793,0.9l-0.632-2.66z"/> - </g> - <g id="g726" fill="#000"> - <path id="path728" d="m26.834,156s5.228-0.23,4.076,1.32c-1.153,1.55-3.602,0.68-3.602,0.68l-0.474-2z"/> - </g> - <g id="g730" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path732" d="m62.434,34.603s-0.726,0.665-0.727-0.406c0-1.07,17.484-14.334,26.327-15.718,0,0-16.099,6.729-25.6,16.124z"/> - </g> - <g id="g734" fill="#000"> - <path id="path736" d="m65.4,98.4s22.001,22.4,31.201,26c0,0,9.199,11.2,5.199,37.2,0,0-3.199,7.6-6.399-13.2,0,0,3.2-25.2-8-9.2,0,0-8.401-9.9-2.001-9.6,0,0,3.201,2,3.601,0.4s-7.601-15.2-24.801-29.6,1.2-2,1.2-2z"/> - </g> - <g id="g738" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path740" d="m7,137.2s-0.2-1.8,1.6-1,96,7,127.6,31c0,0-45.199-23.2-129.2-30z"/> - </g> - <g id="g742" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path744" d="m17.4,132.8s-0.2-1.8,1.6-1,138.4-0.2,162,32.2c0,0-22-25.2-163.6-31.2z"/> - </g> - <g id="g746" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path748" d="m29,128.8s-0.2-1.8,1.6-1,175.2-12.2,198.8,20.2c0,0-9.6-25.6-200.4-19.2z"/> - </g> - <g id="g750" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path752" d="m39,124s-0.2-1.8,1.6-1,124-37.8,147.6-5.4c0,0-13.4-24.6-149.2,6.4z"/> - </g> - <g id="g754" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path756" d="m-19,146.8s-0.2-1.8,1.6-1,19.6,3,21.6,41.8c0,0-7.2-42-23.2-40.8z"/> - </g> - <g id="g758" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path760" d="m-27.8,148.4s-0.2-1.8,1.6-1,16-3.8,13.2,35c0,0,1.2-35.2-14.8-34z"/> - </g> - <g id="g762" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path764" d="m-35.8,148.8s-0.2-1.8,1.6-1,17.2,1.4,4.8,23.8c0,0,9.6-24-6.4-22.8z"/> - </g> - <g id="g766" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path768" d="m11.526,104.46s-0.444,2,1.105,0.79c16.068-12.628,48.51-71.53,104.2-77.164,0,0-38.312-12.11-105.3,76.374z"/> - </g> - <g id="g770" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path772" d="m22.726,102.66s-1.363-1.19,0.505-1.81c1.868-0.63,114.31-73.13,153.6-65.164,0,0-27.11-7.51-154.1,66.974z"/> - </g> - <g id="g774" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path776" d="m1.885,108.77s-0.509,1.6,1.202,0.62c8.975-5.12,12.59-62.331,56.167-63.586,0,0-32.411-14.714-57.369,62.966z"/> - </g> - <g id="g778" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path780" d="m-18.038,119.79s-1.077,1.29,0.876,1.03c10.246-1.33,31.651-42.598,76.09-37.519,0,0-31.966-14.346-76.966,36.489z"/> - </g> - <g id="g782" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path784" d="m-6.8,113.67s-0.811,1.47,1.058,0.84c9.799-3.27,22.883-47.885,67.471-51.432,0,0-34.126-7.943-68.529,50.592z"/> - </g> - <g id="g786" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path788" d="m-25.078,124.91s-0.873,1.04,0.709,0.84c8.299-1.08,25.637-34.51,61.633-30.396,0,0-25.893-11.62-62.342,29.556z"/> - </g> - <g id="g790" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path792" d="m-32.677,130.82s-1.005,1.05,0.586,0.93c4.168-0.31,34.806-33.39,53.274-17.89,0,0-12.015-18.721-53.86,16.96z"/> - </g> - <g id="g794" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path796" d="m36.855,98.898s-1.201-1.355,0.731-1.74c1.932-0.384,122.63-58.097,160.59-45.231,0,0-25.94-10.874-161.32,46.971z"/> - </g> - <g id="g798" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path800" d="m3.4,163.2s-0.2-1.8,1.6-1,17.2,1.4,4.8,23.8c0,0,9.6-24-6.4-22.8z"/> - </g> - <g id="g802" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path804" d="m13.8,161.6s-0.2-1.8,1.6-1,19.6,3,21.6,41.8c0,0-7.2-42-23.2-40.8z"/> - </g> - <g id="g806" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path808" d="m20.6,160s-0.2-1.8,1.6-1,26.4,4.2,50,36.6c0,0-35.6-36.8-51.6-35.6z"/> - </g> - <g id="g810" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path812" d="m28.225,157.97s-0.437-1.76,1.453-1.2c1.89,0.55,22.324-1.35,60.421,32.83,0,0-46.175-34.94-61.874-31.63z"/> - </g> - <g id="g814" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path816" d="m38.625,153.57s-0.437-1.76,1.453-1.2c1.89,0.55,36.724,5.05,88.422,40.03,0,0-74.176-42.14-89.875-38.83z"/> - </g> - <g id="g818" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path820" d="m-1.8,142s-0.2-1.8,1.6-1,55.2,3.4,85.6,30.2c0,0-34.901-24.77-87.2-29.2z"/> - </g> - <g id="g822" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path824" d="m-11.8,146s-0.2-1.8,1.6-1,26.4,4.2,50,36.6c0,0-35.6-36.8-51.6-35.6z"/> - </g> - <g id="g826" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path828" d="m49.503,148.96s-0.565-1.72,1.361-1.3c1.926,0.41,36.996,2.34,91.116,33.44,0,0-77.663-34.4-92.477-32.14z"/> - </g> - <g id="g830" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path832" d="m57.903,146.56s-0.565-1.72,1.361-1.3c1.926,0.41,36.996,2.34,91.116,33.44,0,0-77.063-34.8-92.477-32.14z"/> - </g> - <g id="g834" stroke-width="0.1" stroke="#000" fill="#FFF"> - <path id="path836" d="m67.503,141.56s-0.565-1.72,1.361-1.3c1.926,0.41,44.996,4.74,134.72,39.04,0,0-120.66-40.4-136.08-37.74z"/> - </g> - <g id="g838" fill="#000"> - <path id="path840" d="m-43.8,148.4s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/> - </g> - <g id="g842" fill="#000"> - <path id="path844" d="m-13,162.4s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/> - </g> - <g id="g846" fill="#000"> - <path id="path848" d="m-21.8,162s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/> - </g> - <g id="g850" fill="#000"> - <path id="path852" d="m-117.17,150.18s5.05,1.32,3.39,2.44-3.67-0.42-3.67-0.42l0.28-2.02z"/> - </g> - <g id="g854" fill="#000"> - <path id="path856" d="m-115.17,140.58s5.05,1.32,3.39,2.44-3.67-0.42-3.67-0.42l0.28-2.02z"/> - </g> - <g id="g858" fill="#000"> - <path id="path860" d="m-122.37,136.18s5.05,1.32,3.39,2.44-3.67-0.42-3.67-0.42l0.28-2.02z"/> - </g> - <g id="g862" fill="#CCC"> - <path id="path864" d="m-42.6,211.2-5.6,2c-2,0-13.2,3.6-18.8,13.6,0,0,12.4-9.6,24.4-15.6z"/> - </g> - <g id="g866" fill="#CCC"> - <path id="path868" d="m45.116,303.85c0.141,0.25,0.196,0.67,0.488,0.69,0.658,0.04,1.891,0.34,1.766-0.29-0.848-4.31-1.722-9.25-5.855-11.05-0.639-0.28-2.081,0.13-2.155,1.02-0.127,1.52-0.244,2.87,0.065,4.33,0.3,1.43,2.458,1.43,3.375,0.05,0.936,1.67,1.368,3.52,2.316,5.25z"/> - </g> - <g id="g870" fill="#CCC"> - <path id="path872" d="m34.038,308.58c0.748,1.41,0.621,3.27,2.036,3.84,0.74,0.29,2.59-0.68,2.172-1.76-0.802-2.06-1.19-4.3-2.579-6.11-0.2-0.26,0.04-0.79-0.12-1.12-0.594-1.22-1.739-1.96-3.147-1.63-1.115,2.2,0.033,4.33,1.555,6.04,0.136,0.15-0.03,0.53,0.083,0.74z"/> - </g> - <g id="g874" fill="#CCC"> - <path id="path876" d="m-5.564,303.39c-0.108-0.38-0.146-0.84,0.019-1.16,0.531-1.03,1.324-2.15,0.987-3.18-0.348-1.05-1.464-0.87-2.114-0.3-1.135,0.99-1.184,2.82-1.875,4.18-0.196,0.38-0.145,0.96-0.586,1.35-0.474,0.42-0.914,1.94-0.818,2.51,0.053,0.32-0.13,10.22,0.092,9.96,0.619-0.73,3.669-10.47,3.738-11.36,0.057-0.73,0.789-1.19,0.557-2z"/> - </g> - <g id="g878" fill="#CCC"> - <path id="path880" d="m-31.202,296.6c2.634-2.5,5.424-5.46,4.982-9.17-0.116-0.98-1.891-0.45-2.078,0.39-0.802,3.63-2.841,6.29-5.409,8.68-2.196,2.05-4.058,8.39-4.293,8.9,3.697-5.26,5.954-8,6.798-8.8z"/> - </g> - <g id="g882" fill="#CCC"> - <path id="path884" d="m-44.776,290.64c0.523-0.38,0.221-0.87,0.438-1.2,0.953-1.46,2.254-2.7,2.272-4.44,0.003-0.28-0.375-0.59-0.71-0.36-0.277,0.18-0.619,0.31-0.727,0.44-2.03,2.45-3.43,5.12-4.873,7.93-0.183,0.36-1.327,4.85-1.014,4.96,0.239,0.09,1.959-4.09,2.169-4.21,1.263-0.68,1.275-2.3,2.445-3.12z"/> - </g> - <g id="g886" fill="#CCC"> - <path id="path888" d="m-28.043,310.18c0.444-0.87,2.02-2.07,1.907-2.96-0.118-0.93,0.35-2.37-0.562-1.68-1.257,0.94-4.706,2.29-4.976,8.1-0.026,0.57,2.948-2.12,3.631-3.46z"/> - </g> - <g id="g890" fill="#CCC"> - <path id="path892" d="m-13.6,293c0.4-0.67,1.108-0.19,1.567-0.46,0.648-0.37,1.259-0.93,1.551-1.58,0.97-2.14,2.739-3.96,2.882-6.36-1.491-1.4-2.17,0.64-2.8,1.6-1.323-1.65-2.322,0.23-3.622,0.75-0.07,0.03-0.283-0.32-0.358-0.29-1.177,0.44-1.857,1.52-2.855,2.3-0.171,0.13-0.576-0.05-0.723,0.09-0.652,0.6-1.625,0.93-1.905,1.61-1.11,2.7-4.25,4.8-6.137,12.34,0.381,0.91,4.512-6.64,4.999-7.34,0.836-1.2,0.954,1.66,2.23,1,0.051-0.03,0.237,0.21,0.371,0.34,0.194-0.28,0.412-0.51,0.8-0.4,0-0.4-0.134-0.96,0.067-1.11,1.237-0.98,1.153-2.05,1.933-3.29,0.458,0.79,1.519,0.07,2,0.8z"/> - </g> - <g id="g894" fill="#CCC"> - <path id="path896" d="m46.2,347.4s7.4-20.4,3-31.6c0,0,11.4,21.6,6.8,32.8,0,0-0.4-10.4-4.4-15.4,0,0-4,12.8-5.4,14.2z"/> - </g> - <g id="g898" fill="#CCC"> - <path id="path900" d="m31.4,344.8s5.4-8.8-2.6-27.2c0,0-0.8,20.4-7.6,31.4,0,0,14.2-20.2,10.2-4.2z"/> - </g> - <g id="g902" fill="#CCC"> - <path id="path904" d="m21.4,342.8s-0.2-20,0.2-23c0,0-3.8,16.6-14,26.2,0,0,14.4-12,13.8-3.2z"/> - </g> - <g id="g906" fill="#CCC"> - <path id="path908" d="m11.8,310.8s6,13.6-4,32c0,0,6.4-12.2,1.6-19.2,0,0,2.6-3.4,2.4-12.8z"/> - </g> - <g id="g910" fill="#CCC"> - <path id="path912" d="m-7.4,342.4s-1-15.6,0.8-17.8c0,0,0.2-6.4-0.2-7.4,0,0,4-6.2,4.2,1.2,0,0,1.4,7.8,4.2,12.4,0,0,3.6,5.4,3.4,11.8,0,0-10-30.2-12.4-0.2z"/> - </g> - <g id="g914" fill="#CCC"> - <path id="path916" d="m-11,314.8s-6.6,10.8-8.4,29.8c0,0-1.4-6.2,2.4-20.6,0,0,4.2-15.4,6-9.2z"/> - </g> - <g id="g918" fill="#CCC"> - <path id="path920" d="m-32.8,334.6s5-5.4,6.4-10.4c0,0,3.6-15.8-2.8-7.2,0,0,0.2,8-8,15.4,0,0,4.8-2.4,4.4,2.2z"/> - </g> - <g id="g922" fill="#CCC"> - <path id="path924" d="m-38.6,329.6s3.4-17.4,4.2-18.2c0,0,1.8-3.4-1-0.2,0,0-8.8,19.2-12.8,25.8,0,0,8-9.2,9.6-7.4z"/> - </g> - <g id="g926" fill="#CCC"> - <path id="path928" d="m-44.4,313s11.6-22.4-10.2,3.4c0,0,11-9.8,10.2-3.4z"/> - </g> - <g id="g930" fill="#CCC"> - <path id="path932" d="m-59.8,298.4s4.8-18.8,7.4-18.6l1.6,1.6s-6,9.6-5.4,19.4c0,0-0.6-9.6-3.6-2.4z"/> - </g> - <g id="g934" fill="#CCC"> - <path id="path936" d="m270.5,287s-12-10-14.5-13.5c0,0,13.5,18.5,13.5,25.5,0,0,2.5-7.5,1-12z"/> - </g> - <g id="g938" fill="#CCC"> - <path id="path940" d="m276,265s-21-15-24.5-22.5c0,0,26.5,29.5,26.5,34,0,0,0.5-9-2-11.5z"/> - </g> - <g id="g942" fill="#CCC"> - <path id="path944" d="m293,111s-12-8-13.5-6c0,0,10.5,6.5,13,15,0,0-1.5-9,0.5-9z"/> - </g> - <g id="g946" fill="#CCC"> - <path id="path948" d="m301.5,191.5-17.5-12s19,17,19.5,21l-2-9z"/> - </g> - <g id="g950" stroke="#000"> - <path id="path952" d="m-89.25,169,22,4.75"/> - </g> - <g id="g954" stroke="#000"> - <path id="path956" d="m-39,331s-0.5-3.5-9.5,7"/> - </g> - <g id="g958" stroke="#000"> - <path id="path960" d="m-33.5,336s2-6.5-4.5-2"/> - </g> - <g id="g962" stroke="#000"> - <path id="path964" d="m20.5,344.5s1.5-11-10,2"/> - </g> - </g> -</svg> +<svg xmlns="http://www.w3.org/2000/svg" id="svg2" version="1.1" viewBox="0 0 900 900"><g id="g4" fill="none" transform="matrix(1.7656463,0,0,1.7656463,324.90716,255.00942)"><g id="g6" fill="#FFF" stroke="#000" stroke-width=".172"><path id="path8" d="m-122.3,84.285s0.1,1.894-0.73,1.875c-0.82-0.019-17.27-48.094-37.8-45.851,0,0,17.78-7.353,38.53,43.976z"/></g><g id="g10" fill="#FFF" stroke="#000" stroke-width=".172"><path id="path12" d="m-118.77,81.262s-0.55,1.816-1.32,1.517c-0.77-0.298,0.11-51.104-19.95-55.978,0,0,19.22-0.864,21.27,54.461z"/></g><g id="g14" fill="#FFF" stroke="#000" stroke-width=".172"><path id="path16" d="m-91.284,123.59s1.636,0.96,1.166,1.64c-0.471,0.67-49.642-12.13-59.102,6.23,0,0,3.68-18.89,57.936-7.87z"/></g><g id="g18" fill="#FFF" stroke="#000" stroke-width=".172"><path id="path20" d="m-94.093,133.8s1.856,0.4,1.622,1.19c-0.233,0.79-50.939,4.13-54.129,24.53,0,0-2.46-19.08,52.507-25.72z"/></g><g id="g22" fill="#FFF" stroke="#000" stroke-width=".172"><path id="path24" d="m-98.304,128.28s1.778,0.66,1.432,1.41-50.998-3.34-57.128,16.37c0,0,0.35-19.24,55.696-17.78z"/></g><g id="g26" fill="#FFF" stroke="#000" stroke-width=".172"><path id="path28" d="m-109.01,110.07s1.31,1.38,0.67,1.9-44.38-25.336-58.53-10.29c0,0,8.74-17.147,57.86,8.39z"/></g><g id="g30" fill="#FFF" stroke="#000" stroke-width=".172"><path id="path32" d="m-116.55,114.26s1.45,1.22,0.88,1.81c-0.58,0.59-46.97-20.148-59.32-3.6,0,0,6.74-18.023,58.44,1.79z"/></g><g id="g34" fill="#FFF" stroke="#000" stroke-width=".172"><path id="path36" d="m-119.15,118.34s1.6,1,1.11,1.67c-0.49,0.66-49.27-13.56-59.25,4.51,0,0,4.22-18.77,58.14-6.18z"/></g><g id="g38" fill="#FFF" stroke="#000" stroke-width=".172"><path id="path40" d="m-108.42,118.95s1.12,1.53,0.42,1.97c-0.7,0.43-40.77-30.818-56.73-17.71,0,0,10.87-15.884,56.31,15.74z"/></g><g id="g42" fill="#FFF" stroke="#000" stroke-width=".172"><path id="path44" d="m-128.2,90s0.6,1.8-0.2,2-29.4-41.8-48.6-34.2c0,0,15.2-11.8,48.8,32.2z"/></g><g id="g46" fill="#FFF" stroke="#000" stroke-width=".172"><path id="path48" d="m-127.5,96.979s0.97,1.629,0.23,1.996c-0.74,0.368-37.72-34.476-54.83-22.914,0,0,12.3-14.8,54.6,20.918z"/></g><g id="g50" fill="#FFF" stroke="#000" stroke-width=".172"><path id="path52" d="m-127.62,101.35s1.12,1.53,0.42,1.97c-0.7,0.43-40.77-30.818-56.73-17.713,0,0,10.87-15.881,56.31,15.743z"/></g><g id="g54" fill="#FFF" stroke="#000"><path id="path56" d="m-129.83,103.06c0.5,6.05,1.49,12.62,3.23,15.74,0,0-3.6,12.4,5.2,25.6,0,0-0.4,7.2,1.2,10.4,0,0,4,8.4,8.8,9.2,3.88,0.65,12.607,3.72,22.468,5.12,0,0,17.132,14.08,13.932,26.88,0,0-0.4,16.4-4,18,0,0,11.6-11.2,2,5.6l-4.4,18.8s25.6-21.6,10-3.2l-10,26s19.6-18.4,12.4-10l-3.2,8.8s43.2-27.2,12.4,2.4c0,0,8-3.6,12.4-0.8,0,0,6.8-1.2,6,0.4,0,0-20.8,10.4-24.4,28.8,0,0,8.4-10,5.2,0.8l0.4,11.6s4-21.6,3.6,16c0,0,19.2-18,7.6,2.8v16.8s15.2-16.4,8.8-3.6c0,0,10-8.8,6,6.4,0,0-0.8,10.4,3.6-0.8,0,0,16-30.6,10-4.4,0,0-0.8,19.2,4,4.4,0,0,0.4,10.4,9.6,17.6,0,0-1.2-50.8,11.6-14.8l4,16.4s2.8-9.2,2.4-14.4l8,8s15.2-22.8,12-9.6c0,0-7.6,16-6,20.8,0,0,16.8-34.8,18-36.4,0,0-2,42.4,8.8,6.4,0,0,5.6,12,2.8,16.4,0,0,8-8,7.2-11.2,0,0,4.6-8.2,7.4,5.4,0,0,1.8,9.4,3.4,6.2,0,0,4,24,5.2,1.2,0,0,1.6-13.6-5.6-25.2,0,0,0.8-3.2-2-7.2,0,0,13.6,21.6,6.4-7.2,0,0,11.201,8,12.401,8,0,0-13.601-23.2-4.801-18.4,0,0-5.2-10.4,12.801,1.6,0,0-16.001-16,1.6-6.4,0,0,7.999,6.4,0.4-3.6,0,0-14.401-16,7.599,2,0,0,11.6,16.4,12.4,19.2,0,0-10-29.2-14.4-32,0,0,8.4-36.4,49.6-20.8,0,0,6.8,17.2,11.2-1.2,0,0,12.8-6.4,24,21.2,0,0,4-13.6,3.2-16.4,0,0,6.8,1.2,6,0,0,0,13.2,4.4,14.4,3.6,0,0,6.8,6.8,7.2,3.2,0,0,9.2,2.8,7.2-0.8,0,0,8.8,15.6,9.2,19.2l2.4-14,2,2.8s1.6-7.6,0.8-8.8,20,6.8,24.8,27.6l2,8.4s6-14.8,4.4-18.8c0,0,5.2,0.8,5.6,5.2,0,0,4-23.2-0.8-29.2,0,0,4.4-0.8,5.6,2.8v-7.2s7.2,0.8,7.2-1.6c0,0,4.4-4,6.4,0.8,0,0-12.4-35.2,6-16,0,0,7.2,10.8,3.6-8s-7.6-20.4-2.8-20.8c0,0,0.8-3.6-1.2-5.2s1.2,0,1.2,0,4.8,4-0.4-18c0,0,6.4,1.6-5.6-27.6,0,0,2.8-2.4-1.2-10.8,0,0,8,4.4,10.8,2.8,0,0-0.4-1.6-3.6-5.6,0,0-21.6-54.8-1.2-32.8,0,0,11.85,13.55,5.45-9.25,0,0-9.11-24.009-8.33-28.305l-429.55,23.015z"/></g><g id="g58" fill="#cc7226" stroke="#000"><path id="path60" d="m299.72,80.245c0.62,0.181,2.83,1.305,4.08,2.955,0,0,6.8,10.8,1.6-7.6,0,0-9.2-28.8-0.4-17.6,0,0,6,7.2,2.8-6.4-3.86-16.427-6.4-22.8-6.4-22.8s11.6,4.8-15.2-34.8l8.8,3.6s-19.6-39.6-41.2-44.8l-8-6s38.4-38,25.6-74.8c0,0-6.8-5.2-16.4,4,0,0-6.4,4.8-12.4,3.2,0,0-30.8,1.2-32.8,1.2s-36.8-37.2-102.4-19.6c0,0-5.2,2-9.599,0.8,0,0-18.401-16-67.201,6.8,0,0-10,2-11.6,2s-4.4,0-12.4,6.4-8.4,7.2-10.4,8.8c0,0-16.4,11.2-21.2,12,0,0-11.6,6.4-16,16.4l-3.6,1.2s-1.6,7.2-2,8.4c0,0-4.8,3.6-5.6,9.2,0,0-8.8,6-8.4,10.4,0,0-1.6,5.2-2.4,10,0,0-7.2,4.8-6.4,7.6,0,0-7.6,14-6.4,20.8,0,0-6.4-0.4-9.2,2,0,0-0.8,4.8-2.4,5.2,0,0-2.8,1.2-0.4,5.2,0,0-1.6,2.8-2,4.4,0,0,0.8,2.8-3.6,8.4,0,0-6.4,18.8-4.4,24,0,0,0.4,4.8-2.4,6.4,0,0-3.6-0.4,4.8,11.6,0,0,0.8,1.2-2.4,3.6,0,0-17.2,3.6-19.6,20,0,0-13.6,14.8-13.6,20,0,2.305,0.27,5.452,0.97,10.06,0,0-0.57,8.34,27.03,9.14s402.72-31.355,402.72-31.355z"/></g><g id="g62" fill="#cc7226"><path id="path64" d="m-115.6,102.6c-25-39.4-10.6,17-10.6,17,8.8,34.4,138.4-3.2,138.4-3.2s168.8-30.4,180-34.4,106.4,2.4,106.4,2.4l-5.6-16.8c-64.8-46.4-84-23.2-97.6-27.2s-11.2,5.6-14.4,6.4-42.4-24-48.8-23.2-31.74-22.951-16.8,8.8c16,34-58.4,39.2-75.2,28s7.2,18.4,7.2,18.4c18.4,20-16,3.2-16,3.2-34.4-12.8-58.4,12.8-61.6,13.6s-8,4-8.8-2.4-8.31-23.101-40,3.2c-20,16.6-33.8-5.4-33.8-5.4l-2.8,11.6z"/></g><g id="g66" fill="#e87f3a"><path id="path68" d="m133.51,25.346c-6.4,0.8-31.77-22.939-16.8,8.8,16.6,35.2-58.4,39.2-75.2,28-16.801-11.2,7.2,18.4,7.2,18.4,18.4,20.004-16.001,3.2-16.001,3.2-34.4-12.8-58.4,12.8-61.6,13.6s-8,4.004-8.8-2.4c-0.8-6.4-8.179-22.934-40,3.2-21.236,17.344-34.729-4.109-34.729-4.109l-3.2,10.113c-25-39.804-9.93,18.51-9.93,18.51,8.81,34.4,139.06-4.51,139.06-4.51s168.8-30.404,180-34.404,105.53,2.327,105.53,2.327l-5.53-17.309c-64.8-46.4-83.2-22.618-96.8-26.618s-11.2,5.6-14.4,6.4-42.4-24-48.8-23.2z"/></g><g id="g70" fill="#ea8c4d"><path id="path72" d="m134.82,27.091c-6.4,0.8-31.14-23.229-16.8,8.8,16.2,36.201-58.401,39.201-75.201,28.001s7.2,18.4,7.2,18.4c18.4,19.998-16,3.2-16,3.2-34.4-12.8-58.401,12.8-61.601,13.6s-8,3.998-8.8-2.4c-0.8-6.4-8.048-22.767-40,3.2-22.473,18.088-35.658-2.818-35.658-2.818l-3.6,8.616c-23.8-38.998-9.25,20.02-9.25,20.02,8.8,34.4,139.71-5.82,139.71-5.82s168.8-30.398,180-34.398,104.65,2.254,104.65,2.254l-5.45-17.818c-64.8-46.4-82.4-22.037-96-26.037s-11.2,5.6-14.4,6.401c-3.2,0.8-42.4-24.001-48.8-23.201z"/></g><g id="g74" fill="#ec9961"><path id="path76" d="m136.13,28.837c-6.4,0.8-31.13-23.232-16.8,8.8,16.8,37.556-58.936,38.845-75.202,28-16.8-11.2,7.2,18.4,7.2,18.4,18.4,20.003-16,3.2-16,3.2-34.4-12.8-58.4,12.803-61.6,13.603s-8,4-8.8-2.403c-0.8-6.4-7.917-22.598-40.001,3.203-23.709,18.83-36.587-1.53-36.587-1.53l-4,7.13c-21.8-36.803-8.58,21.52-8.58,21.52,8.8,34.4,140.37-7.12,140.37-7.12s168.8-30.403,180-34.403,103.78,2.182,103.78,2.182l-5.38-18.327c-64.8-46.401-81.6-21.455-95.2-25.455s-11.2,5.6-14.4,6.4-42.4-24-48.8-23.2z"/></g><g id="g78" fill="#eea575"><path id="path80" d="m137.44,30.583c-6.4,0.8-30.63-23.454-16.8,8.8,16.8,39.2-58.403,39.2-75.203,28s7.2,18.4,7.2,18.4c18.4,19.997-16,3.2-16,3.2-34.4-12.8-58.4,12.797-61.6,13.597s-8,4-8.8-2.4c-0.8-6.397-7.785-22.428-40,3.2-24.946,19.58-37.507-0.23-37.507-0.23l-4.4,5.63c-19.8-34.798-7.91,23.04-7.91,23.04,8.8,34.4,141.02-8.44,141.02-8.44s168.8-30.397,180-34.397,102.91,2.109,102.91,2.109l-5.31-18.837c-64.8-46.4-80.8-20.872-94.4-24.872s-11.2,5.6-14.4,6.4-42.4-24-48.8-23.2z"/></g><g id="g82" fill="#f1b288"><path id="path84" d="m138.75,32.328c-6.4,0.8-32.37-22.651-16.8,8.8,19.2,38.8-58.404,39.2-75.204,28s7.2,18.4,7.2,18.4c18.4,20.002-16,3.2-16,3.2-34.4-12.8-58.4,12.802-61.6,13.602s-8,4-8.8-2.4c-0.8-6.402-7.654-22.265-40,3.2-26.182,20.33-38.436,1.05-38.436,1.05l-4.8,4.15c-18-33.202-7.24,24.54-7.24,24.54,8.8,34.4,141.68-9.74,141.68-9.74s168.8-30.402,180-34.402,102.03,2.036,102.03,2.036l-5.23-19.345c-64.8-46.4-80-20.291-93.6-24.291s-11.2,5.6-14.4,6.4-42.4-24-48.8-23.2z"/></g><g id="g86" fill="#f3bf9c"><path id="path88" d="m140.06,34.073c-6.4,0.8-32.75-22.46-16.8,8.8,20.4,40.001-58.405,39.201-75.205,28.001s7.2,18.4,7.2,18.4c18.4,19.996-16,3.2-16,3.2-34.4-12.8-58.4,12.796-61.6,13.596s-8,4-8.8-2.4c-0.8-6.396-7.523-22.092-40,3.2-27.419,21.08-39.365,2.35-39.365,2.35l-5.2,2.65c-16-30.196-6.56,26.06-6.56,26.06,8.8,34.4,142.32-11.06,142.32-11.06s168.8-30.396,180-34.396,101.16,1.963,101.16,1.963l-5.16-19.854c-64.8-46.4-79.2-19.709-92.8-23.709-13.6-4.001-11.2,5.6-14.4,6.4s-42.4-24.001-48.8-23.201z"/></g><g id="g90" fill="#f5ccb0"><path id="path92" d="m141.36,35.819c-6.4,0.8-33.84-21.875-16.8,8.8,22,39.6-58.396,39.2-75.196,28s7.2,18.4,7.2,18.4c18.4,20.001-16,3.2-16,3.2-34.4-12.8-58.4,12.801-61.6,13.601s-8,4-8.8-2.4c-0.8-6.401-7.391-21.928-40,3.2-28.655,21.82-40.294,3.64-40.294,3.64l-5.6,1.16c-14.4-28.401-5.89,27.56-5.89,27.56,8.8,34.4,142.98-12.36,142.98-12.36s168.8-30.401,180-34.401,100.3,1.891,100.3,1.891l-5.1-20.364c-64.8-46.4-78.4-19.127-92-23.127s-11.2,5.6-14.4,6.4-42.4-24-48.8-23.2z"/></g><g id="g94" fill="#f8d8c4"><path id="path96" d="m142.67,37.565c-6.4,0.8-33.84-21.876-16.8,8.8,22,39.6-58.396,39.2-75.196,28s7.2,18.4,7.2,18.4c18.4,19.995-16,3.2-16,3.2-34.401-12.8-58.401,12.795-61.601,13.595s-8,4-8.8-2.4-7.259-21.755-40,3.2c-29.891,22.57-41.213,4.93-41.213,4.93l-6-0.33c-13.61-26.396-5.22,29.08-5.22,29.08,8.8,34.4,143.63-13.68,143.63-13.68s168.8-30.395,180-34.395,99.42,1.818,99.42,1.818l-5.01-20.873c-64.81-46.4-77.61-18.545-91.21-22.545s-11.2,5.6-14.4,6.4-42.4-24-48.8-23.2z"/></g><g id="g98" fill="#fae5d7"><path id="path100" d="m143.98,39.31c-6.4,0.8-33.45-22.087-16.8,8.8,22,40.8-58.397,39.2-75.197,28s7.2,18.4,7.2,18.4c18.4,20-16,3.2-16,3.2-34.4-12.8-58.4,12.8-61.6,13.6-3.201,0.8-8.001,4-8.801-2.4s-7.128-21.592-40,3.2c-31.127,23.31-42.142,6.22-42.142,6.22l-6.4-1.82c-13-24-4.55,30.58-4.55,30.58,8.8,34.4,144.29-14.98,144.29-14.98s168.8-30.4,180-34.4,98.55,1.746,98.55,1.746l-4.95-21.382c-64.8-46.401-76.8-17.964-90.4-21.964s-11.2,5.6-14.4,6.4-42.4-24-48.8-23.2z"/></g><g id="g102" fill="#fcf2eb"><path id="path104" d="m145.29,41.055c-6.4,0.8-32.37-22.644-16.8,8.8,21.2,42.801-58.398,39.201-75.198,28.001s7.2,18.4,7.2,18.4c18.4,20.004-16,3.2-16,3.2-34.4-12.8-58.4,12.804-61.6,13.604s-8,4-8.8-2.4-6.997-21.428-40,3.2c-32.365,24.05-43.072,7.5-43.072,7.5l-6.8-3.3c-12.8-23.204-3.87,32.09-3.87,32.09,8.8,34.4,144.94-16.29,144.94-16.29s168.8-30.4,180-34.404c11.2-4,97.67,1.674,97.67,1.674l-4.87-21.893c-64.8-46.4-76-17.381-89.6-21.381-13.6-4.001-11.2,5.6-14.4,6.4s-42.4-24.001-48.8-23.201z"/></g><g id="g106" fill="#FFF"><path id="path108" d="m-115.8,119.6c-12.8-22-3.2,33.6-3.2,33.6,8.8,34.4,145.6-17.6,145.6-17.6s168.8-30.4,180-34.4,96.8,1.6,96.8,1.6l-4.8-22.4c-64.8-46.4-75.2-16.8-88.8-20.8s-11.2,5.6-14.4,6.4-42.4-24-48.8-23.2-31.62-23.007-16.8,8.8c22.23,47.707-60.759,37.627-75.2,28-16.8-11.2,7.2,18.4,7.2,18.4,18.4,20-16,3.2-16,3.2-34.4-12.8-58.4,12.8-61.6,13.6s-8,4-8.8-2.4-6.865-21.256-40,3.2c-33.6,24.8-44,8.8-44,8.8l-7.2-4.8z"/></g><g id="g110" fill="#000"><path id="path112" d="m-74.2,149.6s-7.2,11.6,13.6,24.8c0,0,1.4,1.4-16.6-2.8,0,0-6.2-2-7.8-12.4,0,0-4.8-4.4-9.6-10s20.4,0.4,20.4,0.4z"/></g><g id="g114" fill="#CCC"><path id="path116" d="m65.8,102s17.698,26.82,17.1,31.6c-1.3,10.4-1.5,20,1.7,24,3.201,4,12.001,37.2,12.001,37.2s-0.4,1.2,11.999-36.8c0,0,11.6-16-8.4-34.4,0,0-35.2-28.8-34.4-21.6z"/></g><g id="g118" fill="#000"><path id="path120" d="m-54.2,176.4s11.2,7.2-3.2,38.4l6.4-2.4s-0.8,11.2-4,13.6l7.2-3.2s4.8,8,0.8,12.8c0,0,16.8,8,16,14.4,0,0,6.4-8,2.4-14.4s-11.2-2.4-10.4-20.8l-8.8,3.2s5.6-8.8,5.6-15.2l-8,2.4s15.469-26.58,4.8-28c-6-0.8-8.8-0.8-8.8-0.8z"/></g><g id="g122" fill="#CCC"><path id="path124" d="m-21.8,193.2s2.8-4.4,0-3.6-34,15.6-40,25.2c0,0,34.4-24.4,40-21.6z"/></g><g id="g126" fill="#CCC"><path id="path128" d="m-11.4,201.2s2.8-4.4,0-3.6-34,15.6-40,25.2c0,0,34.4-24.4,40-21.6z"/></g><g id="g130" fill="#CCC"><path id="path132" d="m1.8,186s2.8-4.4,0-3.6-34,15.6-40,25.2c0,0,34.4-24.4,40-21.6z"/></g><g id="g134" fill="#CCC"><path id="path136" d="m-21.4,229.6s0-6-2.8-5.2-38.8,18.4-44.8,28c0,0,42-25.6,47.6-22.8z"/></g><g id="g138" fill="#CCC"><path id="path140" d="m-20.2,218.8s1.2-4.8-1.6-4c-2,0-28.4,11.6-34.4,21.2,0,0,29.6-21.6,36-17.2z"/></g><g id="g142" fill="#CCC"><path id="path144" d="m-34.6,266.4-10,7.6s10.4-7.6,14-6.4c0,0-6.8,11.2-7.6,16.4,0,0,10.4-12.8,16-12.4,0,0,7.6,0.4,7.6,11.2,0,0,5.6-10.4,8.8-10,0,0,1.2,6.4,0,13.2,0,0,4-7.6,8-6,0,0,6.4-2,5.6,9.6,0,0,0,10.4-0.8,13.2,0,0,5.6-26.4,8-26.8,0,0,8-1.2,12.8,7.6,0,0-4-7.6,0.8-5.6,0,0,10.8,1.6,14,8.4,0,0-6.8-12-1.2-8.8l8,6.4s8.4,21.2,10.4,22.8c0,0-7.6-21.6-6-21.6,0,0-2-12,3.2,2.8,0,0-3.2-14,2.4-13.2s10,10.8,18.4,8.4c0,0,9.601,5.6,11.601-63.6l-124,46.8z"/></g><g id="g146" fill="#000"><path id="path148" d="m-29.8,173.6s14.8-6,54.8,0c0,0,7.2,0.4,14-8.4s33.6-16,40-14l9.601,6.4,0.8,1.2s12.399,10.4,12.799,18-14.399,55.6-24,71.6c-9.6,16-19.2,28.4-38.4,26,0,0-20.8-4-46.4,0,0,0-29.2-1.6-32-9.6s11.2-23.2,11.2-23.2,4.4-8.4,3.2-22.8-0.8-42.4-5.6-45.2z"/></g><g id="g150" fill="#e5668c"><path id="path152" d="M-7.8,175.6c8.4,18.4-21.2,83.6-21.2,83.6-2,1.6,12.66,7.65,22.8,5.2,10.946-2.64,51.2,1.6,51.2,1.6,23.6-15.6,36.4-60,36.4-60s10.401-24-7.2-27.2c-17.6-3.2-82-3.2-82-3.2z"/></g><g id="g154" fill="#b23259"><path id="path156" d="m-9.831,206.5c3.326-12.79,4.91-24.59,2.031-30.9,0,0,62.4,6.4,73.6-14.4,4.241-7.87,19.001,22.8,18.6,32.4,0,0-63,14.4-77.8,3.2l-16.431,9.7z"/></g><g id="g158" fill="#a5264c"><path id="path160" d="m-5.4,222.8s2,7.2-0.4,11.2c0,0-1.6,0.8-2.8,1.2,0,0,1.2,3.6,7.2,5.2,0,0,2,4.4,4.4,4.8s7.2,6,11.2,4.8,15.2-5.2,15.2-5.2,5.6-3.2,14.4,0.4c0,0,2.375-0.8,2.8-4.8,0.5-4.7,3.6-8.4,5.6-10.4s11.6-14.8,10.4-15.2-68,8-68,8z"/></g><g id="g162" fill="#ff727f" stroke="#000"><path id="path164" d="m-9.8,174.4s-2.8,22.4,0.4,30.8,2.4,10.4,1.6,14.4,3.6,14,9.2,20l12,1.6s15.2-3.6,24.4-0.8c0,0,8.994,1.34,12.4-13.6,0,0,4.8-6.4,12-9.2s14.4-44.4,10.4-52.4-18.4-12.4-34.4,3.2-18-1.2-48,6z"/></g><g id="g166" fill="#FFC" stroke="#000" stroke-width=".5"><path id="path168" d="m-8.2,249.2s-0.8-2-5.2-2.4c0,0-22.4-3.6-30.8-16,0,0-6.8-5.6-2.4,6,0,0,10.4,20.4,17.2,23.2,0,0,16.4,4,21.2-10.8z"/></g><g id="g170" fill="#cc3f4c"><path id="path172" d="m71.742,185.23c0.659-7.91,2.612-16.52,0.858-20.03-6.446-12.89-23.419-7.5-34.4,3.2-16,15.6-18-1.2-48,6,0,0-1.745,13.96-0.905,23.98,0,0,37.305-11.58,38.105-5.98,0,0,1.6-3.2,10.8-3.2s31.942-1.17,33.542-3.97z"/></g><g id="g174" stroke="#a51926" stroke-width="2"><path id="path176" d="m28.6,175.2s4.8,4.8,1.2,14.4c0,0-14.4,16-12.4,30"/></g><g id="g178" fill="#FFC" stroke="#000" stroke-width=".5"><path id="path180" d="m-19.4,260s-4.4-12.8,4.4-6l3.6,3.6c-1.2,1.6-6.8,5.6-8,2.4z"/></g><g id="g182" fill="#FFC" stroke="#000" stroke-width=".5"><path id="path184" d="m-14.36,261.2s-3.52-10.24,3.52-4.8l2.88,2.88c-4.56,1.28,0,3.84-6.4,1.92z"/></g><g id="g186" fill="#FFC" stroke="#000" stroke-width=".5"><path id="path188" d="m-9.56,261.2s-3.52-10.24,3.52-4.8l2.88,2.88c-3.36,1.28,0,3.84-6.4,1.92z"/></g><g id="g190" fill="#FFC" stroke="#000" stroke-width=".5"><path id="path192" d="m-2.96,261.4s-3.52-10.24,3.52-4.8c0,0,4.383,2.33,2.881,2.88-2.961,1.08,0,3.84-6.401,1.92z"/></g><g id="g194" fill="#FFC" stroke="#000" stroke-width=".5"><path id="path196" d="m3.52,261.32s-3.52-10.24,3.521-4.8l2.88,2.88c-0.96,1.28,0,3.84-6.401,1.92z"/></g><g id="g198" fill="#FFC" stroke="#000" stroke-width=".5"><path id="path200" d="m10.2,262s-4.8-12.4,4.4-6l3.6,3.6c-1.2,1.6,0,4.8-8,2.4z"/></g><g id="g202" stroke="#a5264c" stroke-width="2"><path id="path204" d="m-18.2,244.8s13.2-2.8,19.2,0.4c0,0,6,1.2,7.2,0.8s4.4-0.8,4.4-0.8"/></g><g id="g206" stroke="#a5264c" stroke-width="2"><path id="path208" d="m15.8,253.6s12-13.6,24-9.2c7.016,2.57,6-0.8,6.8-3.6s1-7,6-10"/></g><g id="g210" fill="#FFC" stroke="#000" stroke-width=".5"><path id="path212" d="m33,237.6s-4-10.8-6.8,2-6,16.4-7.6,19.2c0,0,0,5.2,8.4,4.8,0,0,10.8-0.4,11.2-3.2s-1.2-14.4-5.2-22.8z"/></g><g id="g214" stroke="#a5264c" stroke-width="2"><path id="path216" d="m47,244.8s3.6-2.4,6-1.2"/></g><g id="g218" stroke="#a5264c" stroke-width="2"><path id="path220" d="m53.5,228.4s2.9-4.9,7.7-5.7"/></g><g id="g222" fill="#b2b2b2"><path id="path224" d="m-25.8,265.2s18,3.2,22.4,1.6l0.4,2-20.8-1.2s-11.6-5.6-2-2.4z"/></g><g id="g226" fill="#FFC" stroke="#000" stroke-width=".5"><path id="path228" d="m-11.8,172,19.6,0.8s7.2,30.8,3.6,38.4c0,0-1.2,2.8-4-2.8,0,0-18.4-32.8-21.6-34.8s1.2-1.6,2.4-1.6z"/></g><g id="g230" fill="#FFC" stroke="#000" stroke-width=".5"><path id="path232" d="m-88.9,169.3s8.9,1.7,21.5,4.3c0,0,4.8,22.4,8,27.2s-0.4,4.8-4,2-18.4-16.8-20.4-21.2-5.1-12.3-5.1-12.3z"/></g><g id="g234" fill="#FFC" stroke="#000" stroke-width=".5"><path id="path236" d="m-67.039,173.82s5.8,1.55,6.809,3.76c1.008,2.22-1.202,5.51-1.202,5.51s-1,3.31-2.202,1.15c-1.202-2.17-4.074-9.83-3.405-10.42z"/></g><g id="g238" fill="#000"><path id="path240" d="m-67,173.6s3.6,5.2,7.2,5.2,3.982-0.41,6.8,0.2c4.6,1,4.2-1,10.8,0.2,2.64,0.48,5.2-0.4,8,0.8s6,0.4,7.2-1.6,6-6.2,6-6.2-12.8,1.8-15.6,2.6c0,0-22.4,1.2-30.4-1.2z"/></g><g id="g242" fill="#FFC" stroke="#000" stroke-width=".5"><path id="path244" d="m-22.4,173.8s-6.45,3.5-6.85,5.9,5.25,6.1,5.25,6.1,2.75,4.6,3.35,2.2-0.95-13.8-1.75-14.2z"/></g><g id="g246" fill="#FFC" stroke="#000" stroke-width=".5"><path id="path248" d="m-59.885,179.26s7.007,11.19,7.224-0.02c0,0,0.557-1.26-1.203-1.28-6.075-0.07-4.554-4.18-6.021,1.3z"/></g><g id="g250" fill="#FFC" stroke="#000" stroke-width=".5"><path id="path252" d="m-52.707,179.51s7.921,11.19,7.285-0.09c0,0,0.007-0.33-1.746-0.48-4.747-0.42-4.402-4.94-5.539,0.57z"/></g><g id="g254" fill="#FFC" stroke="#000" stroke-width=".5"><path id="path256" d="m-45.494,179.52s7.96,10.63,7.291,0.96c0,0,0.119-1.23-1.535-1.53-3.892-0.71-4.103-3.95-5.756,0.57z"/></g><g id="g258" fill="#FFC" stroke="#000" stroke-width=".5"><path id="path260" d="m-38.618,179.6s7.9,11.56,8.248,1.78c0,0,1.644-1.38-0.102-1.6-5.818-0.74-5.02-5.19-8.146-0.18z"/></g><g id="g262" fill="#e5e5b2"><path id="path264" d="m-74.792,183.13-7.658-1.53c-2.6-5-4.7-11.15-4.7-11.15s6.35,1,18.85,3.8c0,0,0.876,3.32,2.348,9.11l-8.84-0.23z"/></g><g id="g266" fill="#e5e5b2"><path id="path268" d="m-9.724,178.47c-1.666-2.51-2.983-4.26-3.633-4.67-3.013-1.88,1.13-1.51,2.259-1.51l18.454,0.76s0.524,2.24,1.208,5.63c0,0-10.088-2.01-18.288-0.21z"/></g><g id="g270" fill="#cc7226"><path id="path272" d="m43.88,40.321c27.721,3.96,53.241-31.68,55.001-41.361,1.759-9.68-8.36-21.56-8.36-21.56,1.32-3.08-3.52-17.16-8.8-26.4s-21.181-8.266-38.721-9.24c-15.84-0.88-34.32,22.44-35.64,24.2s4.84,40.041,6.16,45.761-1.32,32.12-1.32,32.12c34.24-9.1,3.96-7.48,31.68-3.52z"/></g><g id="g274" fill="#ea8e51"><path id="path276" d="m8.088-33.392c-1.296,1.728,4.752,39.313,6.048,44.929s-1.296,31.536-1.296,31.536c32.672-8.88,3.888-7.344,31.104-3.456,27.217,3.888,52.273-31.104,54.001-40.609,1.728-9.504-8.208-21.168-8.208-21.168,1.296-3.024-3.456-16.848-8.64-25.92s-20.795-8.115-38.017-9.072c-15.552-0.864-33.696,22.032-34.992,23.76z"/></g><g id="g278" fill="#efaa7c"><path id="path280" d="m8.816-32.744c-1.272,1.696,4.664,38.585,5.936,44.097s-1.272,30.952-1.272,30.952c31.404-9.16,3.816-7.208,30.528-3.392,26.713,3.816,51.305-30.528,53.001-39.857,1.696-9.328-8.056-20.776-8.056-20.776,1.272-2.968-3.392-16.536-8.48-25.44s-20.41-7.965-37.313-8.904c-15.264-0.848-33.072,21.624-34.344,23.32z"/></g><g id="g282" fill="#f4c6a8"><path id="path284" d="m9.544-32.096c-1.248,1.664,4.576,37.857,5.824,43.265s-1.248,30.368-1.248,30.368c29.436-9.04,3.744-7.072,29.952-3.328,26.209,3.744,50.337-29.952,52.001-39.104,1.664-9.153-7.904-20.385-7.904-20.385,1.248-2.912-3.328-16.224-8.32-24.96s-20.025-7.815-36.609-8.736c-14.976-0.832-32.448,21.216-33.696,22.88z"/></g><g id="g286" fill="#f9e2d3"><path id="path288" d="m10.272-31.448c-1.224,1.632,4.488,37.129,5.712,42.433s-1.224,29.784-1.224,29.784c27.868-8.92,3.672-6.936,29.376-3.264,25.705,3.672,49.369-29.376,51.001-38.353,1.632-8.976-7.752-19.992-7.752-19.992,1.224-2.856-3.264-15.912-8.16-24.48s-19.64-7.665-35.905-8.568c-14.688-0.816-31.824,20.808-33.048,22.44z"/></g><g id="g290" fill="#FFF"><path id="path292" d="M44.2,36.8c25.2,3.6,48.401-28.8,50.001-37.6s-7.6-19.6-7.6-19.6c1.2-2.8-3.201-15.6-8.001-24s-19.254-7.514-35.2-8.4c-14.4-0.8-31.2,20.4-32.4,22s4.4,36.4,5.6,41.6-1.2,29.2-1.2,29.2c25.5-8.6,3.6-6.8,28.8-3.2z"/></g><g id="g294" fill="#CCC"><path id="path296" d="m90.601,2.8s-27.801,7.6-39.401,6c0,0-15.8-6.6-24.6,15.2,0,0-3.6,7.2-5.6,9.2s69.601-30.4,69.601-30.4z"/></g><g id="g298" fill="#000"><path id="path300" d="m94.401,0.6s-29.001,12.2-39.001,11.8c0,0-16.4-4.6-24.8,10,0,0-8.4,9.2-11.6,10.8,0,0-0.4,1.6,6-2.4l10.4,5.2s14.8,9.6,24.4-6.4c0,0,4-11.2,4-13.2s21.2-7.6,22.801-8c1.6-0.4,8.2-4.6,7.8-7.8z"/></g><g id="g302" fill="#99cc32"><path id="path304" d="m47,36.514c-6.872,0-15.245-3.865-15.245-10.114,0-6.248,8.373-12.513,15.245-12.513,6.874,0,12.446,5.065,12.446,11.313,0,6.249-5.572,11.314-12.446,11.314z"/></g><g id="g306" fill="#659900"><path id="path308" d="m43.377,19.83c-4.846,0.722-9.935,2.225-9.863,2.009,1.54-4.619,7.901-7.952,13.486-7.952,4.296,0,8.084,1.978,10.32,4.988,0,0-5.316-0.33-13.943,0.955z"/></g><g id="g310" fill="#FFF"><path id="path312" d="m55.4,19.6s-4.4-3.2-4.4-1c0,0,3.6,4.4,4.4,1z"/></g><g id="g314" fill="#000"><path id="path316" d="m45.4,27.726c-2.499,0-4.525-2.026-4.525-4.526,0-2.499,2.026-4.525,4.525-4.525,2.5,0,4.526,2.026,4.526,4.525,0,2.5-2.026,4.526-4.526,4.526z"/></g><g id="g318" fill="#cc7226"><path id="path320" d="m-58.6,14.4s-3.2-21.2-0.8-25.6c0,0,10.8-10,10.4-13.6,0,0-0.4-18-1.6-18.8s-8.8-6.8-14.8-0.4c0,0-10.4,18-9.6,24.4v2s-7.6-0.4-9.2,1.6c0,0-1.2,5.2-2.4,5.6,0,0-2.8,2.4-0.8,5.2,0,0-2,2.4-1.6,6.4l7.6,4s2,14.4,12.8,19.6c4.836,2.329,8-4.4,10-10.4z"/></g><g id="g322" fill="#FFF"><path id="path324" d="m-59.6,12.56s-2.88-19.08-0.72-23.04c0,0,9.72-9,9.36-12.24,0,0-0.36-16.2-1.44-16.92s-7.92-6.12-13.32-0.36c0,0-9.36,16.2-8.64,21.96v1.8s-6.84-0.36-8.28,1.44c0,0-1.08,4.68-2.16,5.04,0,0-2.52,2.16-0.72,4.68,0,0-1.8,2.16-1.44,5.76l6.84,3.6s1.8,12.96,11.52,17.64c4.352,2.095,7.2-3.96,9-9.36z"/></g><g id="g326" fill="#eb955c"><path id="path328" d="m-51.05-42.61c-1.09-0.86-8.58-6.63-14.43-0.39,0,0-10.14,17.55-9.36,23.79v1.95s-7.41-0.39-8.97,1.56c0,0-1.17,5.07-2.34,5.46,0,0-2.73,2.34-0.78,5.07,0,0-1.95,2.34-1.56,6.24l7.41,3.9s1.95,14.04,12.48,19.11c4.714,2.27,7.8-4.29,9.75-10.14,0,0-3.12-20.67-0.78-24.96,0,0,10.53-9.75,10.14-13.26,0,0-0.39-17.55-1.56-18.33z"/></g><g id="g330" fill="#f2b892"><path id="path332" d="m-51.5-41.62c-0.98-0.92-8.36-6.46-14.06-0.38,0,0-9.88,17.1-9.12,23.18v1.9s-7.22-0.38-8.74,1.52c0,0-1.14,4.94-2.28,5.32,0,0-2.66,2.28-0.76,4.94,0,0-1.9,2.28-1.52,6.08l7.22,3.8s1.9,13.68,12.16,18.62c4.594,2.212,7.6-4.18,9.5-9.88,0,0-3.04-20.14-0.76-24.32,0,0,10.26-9.5,9.88-12.92,0,0-0.38-17.1-1.52-17.86z"/></g><g id="g334" fill="#f8dcc8"><path id="path336" d="m-51.95-40.63c-0.87-0.98-8.14-6.29-13.69-0.37,0,0-9.62,16.65-8.88,22.57v1.85s-7.03-0.37-8.51,1.48c0,0-1.11,4.81-2.22,5.18,0,0-2.59,2.22-0.74,4.81,0,0-1.85,2.22-1.48,5.92l7.03,3.7s1.85,13.32,11.84,18.13c4.473,2.154,7.4-4.07,9.25-9.62,0,0-2.96-19.61-0.74-23.68,0,0,9.99-9.25,9.62-12.58,0,0-0.37-16.65-1.48-17.39z"/></g><g id="g338" fill="#FFF"><path id="path340" d="m-59.6,12.46s-2.88-18.98-0.72-22.94c0,0,9.72-9,9.36-12.24,0,0-0.36-16.2-1.44-16.92-0.76-1.04-7.92-6.12-13.32-0.36,0,0-9.36,16.2-8.64,21.96v1.8s-6.84-0.36-8.28,1.44c0,0-1.08,4.68-2.16,5.04,0,0-2.52,2.16-0.72,4.68,0,0-1.8,2.16-1.44,5.76l6.84,3.6s1.8,12.96,11.52,17.64c4.352,2.095,7.2-4.06,9-9.46z"/></g><g id="g342" fill="#CCC"><path id="path344" d="m-62.7,6.2s-21.6-10.2-22.5-11c0,0,9.1,8.2,9.9,8.2s12.6,2.8,12.6,2.8z"/></g><g id="g346" fill="#000"><path id="path348" d="m-79.8,0s18.4,3.6,18.4,8c0,2.912-0.243,16.331-5.6,14.8-8.4-2.4-4.8-16.8-12.8-22.8z"/></g><g id="g350" fill="#99cc32"><path id="path352" d="m-71.4,3.8s8.978,1.474,10,4.2c0.6,1.6,1.263,9.908-4.2,11-4.552,0.911-6.782-9.31-5.8-15.2z"/></g><g id="g354" fill="#000"><path id="path356" d="m14.595,46.349c-0.497-1.742,0.814-1.611,2.605-2.149,2-0.6,14.2-4.4,15-7s14,1.8,14,1.8c1.8,0.8,6.2,3.4,6.2,3.4,4.8,1.2,11.4,1.6,11.4,1.6,2.4,1,5.8,3.8,5.8,3.8,14.6,10.2,27.001,3,27.001,3,19.999-6.6,13.999-23.8,13.999-23.8-3-9,0.2-12.4,0.2-12.4,0.2-3.8,7.4,2.6,7.4,2.6,2.6,4.2,3.4,9.2,3.4,9.2,8,11.2,4.6-6.6,4.6-6.6,0.2-1-2.6-4.6-2.6-5.8s-1.8-4.6-1.8-4.6c-3-3.4-0.6-10.4-0.6-10.4,1.8-13.8-0.4-12-0.4-12-1.2-1.8-10.4,8.2-10.4,8.2-2.2,3.4-8.2,5-8.2,5-2.799,1.8-6.199,0.4-6.199,0.4-2.6-0.4-8.2,6.6-8.2,6.6,2.8-0.2,5.2,4.2,7.6,4.4s4.2-2.4,5.799-3c1.6-0.6,4.4,5.2,4.4,5.2,0.4,2.6-5.2,7.4-5.2,7.4-0.4,4.6-1.999,3-1.999,3-3-0.6-4.2,3.2-5.2,7.8s-5.2,5-5.2,5c-1.6,7.4-2.801,4.4-2.801,4.4-0.2-5.6-6.2,0.2-6.2,0.2-1.2,2-5.8-0.2-5.8-0.2-6.8-2-4.4-4-4.4-4,1.8-2.2,13,0,13,0,2.2-1.6-5.8-5.6-5.8-5.6-0.6-1.8,0.4-6.2,0.4-6.2,1.2-3.2,8-8.8,8-8.8,9.401-1.2,6.601-2.8,6.601-2.8-6.2-5.2-12.001,2.4-12.001,2.4-2.2,6.2-19.6,21.2-19.6,21.2-4.8,3.4-2.2-3.4-6.2,0s-24.6-5.6-24.6-5.6c-11.562-1.193-14.294,14.549-17.823,11.429,0,0,5.418,8.52,3.818,2.92z"/></g><g id="g358" fill="#000"><path id="path360" d="m209.4-120s-25.6,8-28.4,26.8c0,0-2.4,22.8,18,40.4,0,0,0.4,6.4,2.4,9.6,0,0-1.6,4.8,17.2-2.8l27.2-8.4s6.4-2.4,11.6-11.2,20.4-27.6,16.8-52.8c0,0,1.2-11.2-4.8-11.6,0,0-8.4-1.6-15.6,6,0,0-6.8,3.2-9.2,2.8l-35.2,1.2z"/></g><g id="g362" fill="#000"><path id="path364" d="m264.02-120.99s2.1-8.93-2.74-4.09c0,0-7.04,5.72-14.52,5.72,0,0-14.52,2.2-18.92,15.4,0,0-3.96,26.84,3.96,32.56,0,0,4.84,7.48,11.88,0.88s22.54-36.83,20.34-50.47z"/></g><g id="g366" fill="#323232"><path id="path368" d="m263.65-120.63s2.09-8.75-2.66-3.99c0,0-6.92,5.61-14.26,5.61,0,0-14.26,2.16-18.58,15.12,0,0-3.89,26.354,3.89,31.97,0,0,4.75,7.344,11.66,0.864,6.92-6.48,22.11-36.184,19.95-49.574z"/></g><g id="g370" fill="#666"><path id="path372" d="m263.27-120.27s2.08-8.56-2.58-3.9c0,0-6.78,5.51-13.99,5.51,0,0-14,2.12-18.24,14.84,0,0-3.81,25.868,3.82,31.38,0,0,4.66,7.208,11.45,0.848,6.78-6.36,21.66-35.538,19.54-48.678z"/></g><g id="g374" fill="#999"><path id="path376" d="m262.9-119.92s2.07-8.37-2.51-3.79c0,0-6.65,5.41-13.73,5.41,0,0-13.72,2.08-17.88,14.56,0,0-3.75,25.372,3.74,30.78,0,0,4.58,7.072,11.23,0.832,6.66-6.24,21.23-34.892,19.15-47.792z"/></g><g id="g378" fill="#CCC"><path id="path380" d="m262.53-119.56s2.06-8.18-2.43-3.7c0,0-6.53,5.31-13.47,5.31,0,0-13.46,2.04-17.54,14.28,0,0-3.67,24.886,3.67,30.19,0,0,4.49,6.936,11.02,0.816,6.52-6.12,20.79-34.246,18.75-46.896z"/></g><g id="g382" fill="#FFF"><path id="path384" d="m262.15-119.2s2.05-8-2.35-3.6c0,0-6.4,5.2-13.2,5.2,0,0-13.2,2-17.2,14,0,0-3.6,24.4,3.6,29.6,0,0,4.4,6.8,10.8,0.8s20.35-33.6,18.35-46z"/></g><g id="g386" fill="#992600"><path id="path388" d="m50.6,84s-20.4-19.2-28.4-20c0,0-34.4-4-49.2,14,0,0,17.6-20.4,45.2-14.8,0,0-21.6-4.4-34-1.2l-26.4,14-2.8,4.8s4-14.8,22.4-20.8c0,0,22.8-4.8,33.6,0,0,0-21.6-6.8-31.6-4.8,0,0-30.4-2.4-43.2,24,0,0,4-14.4,18.8-21.6,0,0,13.6-8.8,34-6,0,0,14.4,3.2,19.6,5.6s4-0.4-4.4-5.2c0,0-5.6-10-19.6-9.6,0,0-42.8,3.6-53.2,15.6,0,0,13.6-11.2,24-14,0,0,22.4-8,30.8-7.2,0,0,24.8,1,32.4-3,0,0-11.2,5-8,8.2s10,10.8,10,12,24.2,23.3,27.8,27.7l2.2,2.3z"/></g><g id="g390" fill="#CCC"><path id="path392" d="m189,278s-15.5-36.5-28-46c0,0,26,16,29.5,34,0,0,0,10-1.5,12z"/></g><g id="g394" fill="#CCC"><path id="path396" d="m236,285.5s-26.5-55-45-79c0,0,43.5,37.5,48.5,64l0.5,5.5-3-2.5s-0.5,9-1,12z"/></g><g id="g398" fill="#CCC"><path id="path400" d="m292.5,237s-62.5-59.5-64-62c0,0,60.5,66,63.5,73.5,0,0-2-9,0.5-11.5z"/></g><g id="g402" fill="#CCC"><path id="path404" d="m104,280.5s19.5-52,38.5-29.5c0,0,15,10,14.5,13,0,0-4-6.5-22-6,0,0-19-3-31,22.5z"/></g><g id="g406" fill="#CCC"><path id="path408" d="m294.5,153s-45-28.5-52.5-30c-11.81-2.36,49.5,29,54.5,39.5,0,0,2-2.5-2-9.5z"/></g><g id="g410" fill="#000"><path id="path412" d="m143.8,259.6s20.4-2,27.2-8.8l4.4,3.6,17.6-38.4,3.6,5.2s14.4-14.8,13.6-22.8,12.8,6,12.8,6-0.8-11.6,6.4-4.8c0,0-2.4-15.6,6-7.6,0,0-10.54-30.16,12-4.4,5.6,6.4,1.2-0.4,1.2-0.4s-26-48-4.4-33.6c0,0,2-22.8,0.8-27.2s-3.2-26.8-8-32,0.4-6.8,6-1.6c0,0-11.2-24,2-12,0,0-3.6-15.2-8-18,0,0-5.6-17.2,9.6-6.4,0,0-4.4-12.4-7.6-15.6,0,0-11.6-27.6-4.4-22.8l4.4,3.6s-6.8-14-0.4-9.6,6.4,4,6.4,4-21.2-33.2-0.8-15.6c0,0-8.16-13.918-11.6-20.8,0,0-18.8-20.4-4.4-14l4.8,1.6s-8.8-10-16.8-11.6,2.4-8,8.8-6,22,9.6,22,9.6,12.8,18.8,16.8,19.2c0,0-20-7.6-14,0.4,0,0,14.4,14,7.2,13.6,0,0-6,7.2-1.2,16,0,0-18.46-18.391-3.6,7.2l6.8,16.4s-24.4-24.8-13.2-2.8c0,0,17.2,23.6,19.2,24s6.4,9.2,6.4,9.2l-4.4-2,5.2,8.8s-11.2-12-5.2,1.2l5.6,14.4s-20.4-22-6.8,7.6c0,0-16.4-5.2-7.6,12,0,0-1.6,16-1.2,21.2s1.6,33.6-2.8,41.6,6,27.2,8,31.2,5.6,14.8-3.2,5.6-4.4-3.6-2.4,5.2,8,24.4,7.2,30c0,0-1.2,1.2-4.4-2.4,0,0-14.8-22.8-13.2-8.4,0,0-1.2,8-4.4,16.8,0,0-3.2,10.8-3.2,2,0,0-3.2-16.8-6-9.2s-6.4,13.6-9.2,16-8-20.4-9.2-10c0,0-12-12.4-16.8,4l-11.6,16.4s-0.4-12.4-1.6-6.4c0,0-30,6-40.4,1.6z"/></g><g id="g414" fill="#000"><path id="path416" d="m109.4-97.2s-11.599-8-15.599-7.6,27.599-8.8,68.799,18.8c0,0,4.8,2.8,8.4,2.4,0,0,3.2,2.4,0.4,6,0,0-8.8,9.6,2.4,20.8,0,0,18.4,6.8,12.8-2,0,0,10.8,4,13.2,8s1.2,0,1.2,0l-12.4-12.4s-5.2-2-8-10.4-5.2-18.4-0.8-21.6c0,0-4,4.4-3.2,0.4s4.4-7.6,6-8,18-16.2,24.8-16.6c0,0-9.2,1.4-12.2,0.4s-29.6-12.4-35.6-13.6c0,0-16.8-6.6-4.8-4.6,0,0,35.8,3.8,54,17,0,0-7.2-8.4-25.6-15.4,0,0-22.2-12.6-57.4-7.6,0,0-17.8,3.2-25.6,5,0,0-2.599-0.6-3.199-1s-12.401-9.4-40.001-2.4c0,0-17,4.6-25.6,9.4,0,0-15.2,1.2-18.8,4.4,0,0-18.6,14.6-20.6,15.4s-13.4,8.4-14.2,8.8c0,0,24.6-6.6,27-9s19.8-5,22.2-3.6,10.8,0.8,1.2,1.4c0,0,75.6,14.8,76.4,16.8s4.8,0.8,4.8,0.8z"/></g><g id="g418" fill="#cc7226"><path id="path420" d="m180.8-106.4s-10.2-7.4-12.2-7.4-14.4-10.2-18.6-9.8-16.4-9.6-43.8-1.4c0,0-0.6-2,3-2.8,0,0,6.4-2.2,6.8-2.8,0,0,20.2-4.2,27.4-0.6,0,0,9.2,2.6,15.4,8.8,0,0,11.2,3.2,14.4,2.2,0,0,8.8,2.2,9.2,4,0,0,5.8,3,4,5.6,0,0,0.4,1.6-5.6,4.2z"/></g><g id="g422" fill="#cc7226"><path id="path424" d="m168.33-108.51c0.81,0.63,1.83,0.73,2.43,1.54,0.24,0.31-0.05,0.64-0.37,0.74-1.04,0.31-2.1-0.26-3.24,0.33-0.4,0.21-1.04,0.03-1.6-0.12-1.63-0.44-3.46-0.47-5.15,0.22-1.98-1.13-4.34-0.54-6.42-1.55-0.06-0.02-0.28,0.32-0.36,0.3-3.04-1.15-6.79-0.87-9.22-3.15-2.43-0.41-4.78-0.87-7.21-1.55-1.82-0.51-3.23-1.5-4.85-2.33-1.38-0.71-2.83-1.23-4.37-1.61-1.86-0.45-3.69-0.34-5.58-0.86-0.1-0.02-0.29,0.32-0.37,0.3-0.32-0.11-0.62-0.69-0.79-0.64-1.68,0.52-3.17-0.45-4.83-0.11-1.18-1.22-2.9-0.98-4.45-1.42-2.97-0.85-6.12,0.42-9.15-0.58,4.11-1.84,8.8-0.61,12.86-2.68,2.33-1.18,4.99-0.08,7.56-0.84,0.49-0.15,1.18-0.35,1.58,0.32,0.14-0.14,0.32-0.37,0.38-0.35,2.44,1.16,4.76,2.43,7.24,3.5,0.34,0.15,0.88-0.09,1.13,0.12,1.52,1.21,3.46,1.11,4.85,2.33,1.7-0.5,3.49-0.12,5.22-0.75,0.08-0.02,0.31,0.32,0.34,0.3,1.14-0.75,2.29-0.48,3.18-0.18,0.34,0.12,1,0.37,1.31,0.44,1.12,0.27,1.98,0.75,3.16,0.94,0.11,0.02,0.3-0.32,0.37-0.3,1.12,0.44,2.16,0.39,2.82,1.55,0.14-0.14,0.3-0.37,0.38-0.35,1.03,0.34,1.68,1.1,2.78,1.34,0.48,0.1,1.1,0.73,1.67,0.91,2.39,0.73,4.24,2.26,6.43,3.15,0.76,0.31,1.64,0.55,2.27,1.04z"/></g><g id="g426" fill="#cc7226"><path id="path428" d="m91.696-122.74c-2.518-1.72-4.886-2.83-7.328-4.62-0.181-0.13-0.541,0.04-0.743-0.08-1.007-0.61-1.895-1.19-2.877-1.89-0.539-0.38-1.36-0.37-1.868-0.63-2.544-1.29-5.173-1.85-7.68-3.04,0.682-0.64,1.804-0.39,2.4-1.2,0.195,0.28,0.433,0.56,0.786,0.37,1.678-0.9,3.528-1.05,5.204-0.96,1.704,0.09,3.424,0.39,5.199,0.67,0.307,0.04,0.506,0.56,0.829,0.66,2.228,0.66,4.617,0.14,6.736,0.98,1.591,0.63,3.161,1.45,4.4,2.72,0.252,0.26-0.073,0.57-0.353,0.76,0.388-0.11,0.661,0.1,0.772,0.41,0.084,0.24,0.084,0.54,0,0.78-0.112,0.31-0.391,0.41-0.765,0.46-1.407,0.19,0.365-1.19-0.335-0.74-1.273,0.82-0.527,2.22-1.272,3.49-0.28-0.19-0.51-0.41-0.4-0.8,0.234,0.52-0.368,0.81-0.536,1.13-0.385,0.72-1.284,2.14-2.169,1.53z"/></g><g id="g430" fill="#cc7226"><path id="path432" d="m59.198-115.39c-3.154-0.79-6.204-0.68-9.22-1.96-0.067-0.02-0.29,0.32-0.354,0.3-1.366-0.6-2.284-1.56-3.36-2.61-0.913-0.89-2.571-0.5-3.845-0.99-0.324-0.12-0.527-0.63-0.828-0.67-1.219-0.16-2.146-1.11-3.191-1.68,2.336-0.8,4.747-0.76,7.209-1.15,0.113-0.02,0.258,0.31,0.391,0.31,0.136,0,0.266-0.23,0.4-0.36,0.195,0.28,0.497,0.61,0.754,0.35,0.548-0.54,1.104-0.35,1.644-0.31,0.144,0.01,0.269,0.32,0.402,0.32,0.136,0,0.267-0.32,0.4-0.32,0.136,0,0.267,0.32,0.4,0.32,0.136,0,0.266-0.23,0.4-0.36,0.692,0.78,1.577,0.23,2.399,0.41,1.038,0.22,1.305,1.37,2.379,1.67,4.715,1.3,8.852,3.45,13.215,5.54,0.307,0.14,0.517,0.39,0.407,0.78,0.267,0,0.58-0.09,0.77,0.04,1.058,0.74,2.099,1.28,2.796,2.38,0.216,0.34-0.113,0.75-0.346,0.7-4.429-1-8.435-1.61-12.822-2.71z"/></g><g id="g434" fill="#cc7226"><path id="path436" d="m45.338-71.179c-1.592-1.219-2.176-3.25-3.304-5.042-0.214-0.34,0.06-0.654,0.377-0.743,0.56-0.159,1.103,0.319,1.512,0.521,1.745,0.862,3.28,2.104,5.277,2.243,1.99,2.234,6.25,2.619,6.257,6,0.001,0.859-1.427-0.059-1.857,0.8-2.451-1.003-4.84-0.9-7.22-2.367-0.617-0.381-0.287-0.834-1.042-1.412z"/></g><g id="g438" fill="#cc7226"><path id="path440" d="m17.8-123.76c0.135,0,7.166,0.24,7.149,0.35-0.045,0.31-7.775,1.36-8.139,1.19-0.164-0.08-7.676,2.35-7.81,2.22,0.268-0.14,8.534-3.76,8.8-3.76z"/></g><g id="g442" fill="#000"><path id="path444" d="m33.2-114s-14.8,1.8-19.2,3-23,8.8-26,10.8c0,0-13.4,5.4-30.4,25.4,0,0,7.6-3.4,9.8-6.2,0,0,13.6-12.6,13.4-10,0,0,12.2-8.6,11.6-6.4,0,0,24.4-11.2,22.4-8,0,0,21.6-4.6,20.6-2.6,0,0,18.8,4.4,16,4.6,0,0-5.8,1.2,0.6,4.8,0,0-3.4,4.4-8.8,0.4s-2.4-1.8-7.4-0.8c0,0-2.6,0.8-7.2-3.2,0,0-5.6-4.6-14.4-1,0,0-30.6,12.6-32.6,13.2,0,0-3.6,2.8-6,6.4,0,0-5.8,4.4-8.8,5.8,0,0-12.8,11.6-14,13,0,0-3.4,5.2-4.2,5.6,0,0,6.4-3.8,8.4-5.8,0,0,14-10,19.4-10.8,0,0,4.4-3,5.2-4.4,0,0,14.4-9.2,18.6-9.2,0,0,9.2,5.2,11.6-1.8,0,0,5.8-1.8,11.4-0.6,0,0,3.2-2.6,2.4-4.8,0,0,1.6-1.8,2.6,2,0,0,3.4,3.6,8.2,1.6,0,0,4-0.2,2,2.2,0,0-4.4,3.8-16.2,4,0,0-12.4,0.6-28.8,8.2,0,0-29.8,10.4-39,20.8,0,0-6.4,8.8-11.8,10,0,0-5.8,0.8-11.8,8.2,0,0,9.8-5.8,18.8-5.8,0,0,4-2.4,0.2,1.2,0,0-3.6,7.6-2,13,0,0-0.6,5.2-1.4,6.8,0,0-7.8,12.8-7.8,15.2s1.2,12.2,1.6,12.8-1-1.6,2.8,0.8,6.6,4,7.4,6.8-2-5.4-2.2-7.2-4.4-9-3.6-11.4c0,0,1,1,1.8,2.4,0,0-0.6-0.6,0-4.2,0,0,0.8-5.2,2.2-8.4s3.4-7,3.8-7.8,0.4-6.6,1.8-4l3.4,2.6s-2.8-2.6-0.6-4.8c0,0-1-5.6,0.8-8.2,0,0,7-8.4,8.6-9.4s0.2-0.6,0.2-0.6,6-4.2,0.2-2.6c0,0-4,1.6-7,1.6,0,0-7.6,2-3.6-2.2s14-9.6,17.8-9.4l0.8,1.6,11.2-2.4-1.2,0.8s-0.2-0.2,4-0.6,10,1,11.4-0.8,4.8-2.8,4.4-1.4-0.6,3.4-0.6,3.4,5-5.8,4.4-3.6-8.8,7.4-10.2,13.6l10.4-8.2,3.6-3s3.6,2.2,3.8,0.6,4.8-7.4,6-7.2,3.2-2.6,3,0,7.4,8,7.4,8,3.2-1.8,4.6-0.4,5.6-19.8,5.6-19.8l25-10.6,43.6-3.4-16.999-6.8-61.001-11.4z"/></g><g id="g446" stroke="#4c0000" stroke-width="2"><path id="path448" d="m51.4,85s-15-16.8-23.4-19.4c0,0-13.4-6.8-38,1"/></g><g id="g450" stroke="#4c0000" stroke-width="2"><path id="path452" d="m24.8,64.2s-25.2-8-40.6-3.8c0,0-18.4,2-26.8,15.8"/></g><g id="g454" stroke="#4c0000" stroke-width="2"><path id="path456" d="m21.2,63s-17-7.2-31.8-9.4c0,0-16.6-2.6-33.2,4.6,0,0-12.2,6-17.6,16.2"/></g><g id="g458" stroke="#4c0000" stroke-width="2"><path id="path460" d="m22.2,63.4s-15.4-11-16.4-12.4c0,0-7-11-20-11.4,0,0-21.4,0.8-38.6,8.8"/></g><g id="g462" fill="#000"><path id="path464" d="M20.895,54.407c1.542,1.463,28.505,30.393,28.505,30.393,35.2,36.6,7.2,2.4,7.2,2.4-7.6-4.8-16.8-23.6-16.8-23.6-1.2-2.8,14,7.2,14,7.2,4,0.8,17.6,20,17.6,20-6.8-2.4-2,4.8-2,4.8,2.8,2,23.201,17.6,23.201,17.6,3.6,4,7.599,5.6,7.599,5.6,14-5.2,7.6,8,7.6,8,2.4,6.8,8-4.8,8-4.8,11.2-16.8-5.2-14.4-5.2-14.4-30,2.8-36.8-13.2-36.8-13.2-2.4-2.4,6.4,0,6.4,0,8.401,2-7.2-12.4-7.2-12.4,2.4,0,11.6,6.8,11.6,6.8,10.401,9.2,12.401,7.2,12.401,7.2,17.999-8.8,28.399-1.2,28.399-1.2,2,1.6-3.6,8.4-2,13.6s6.4,17.6,6.4,17.6c-2.4,1.6-2,12.4-2,12.4,16.8,23.2,7.2,21.2,7.2,21.2-15.6-0.4-0.8,7.2-0.8,7.2,3.2,2,12,9.2,12,9.2-2.8-1.2-4.4,4-4.4,4,4.8,4,2,8.8,2,8.8-6,1.2-7.2,5.2-7.2,5.2,6.8,8-3.2,8.4-3.2,8.4,3.6,4.4-1.2,16.4-1.2,16.4-4.8,0-11.2,5.6-11.2,5.6,2.4,4.8-8,10.4-8,10.4-8.4,1.6-5.6,8.4-5.6,8.4-7.999,6-10.399,22-10.399,22-0.8,10.4-3.2,13.6,2,11.6,5.199-2,4.399-14.4,4.399-14.4-4.799-15.6,38-31.6,38-31.6,4-1.6,4.8-6.8,4.8-6.8,2,0.4,10.8,8,10.8,8,7.6,11.2,8,2,8,2,1.2-3.6-0.4-9.6-0.4-9.6,6-21.6-8-28-8-28-10-33.6,4-25.2,4-25.2,2.8,5.6,13.6,10.8,13.6,10.8l3.6-2.4c-1.6-4.8,6.8-10.8,6.8-10.8,2.8,6.4,8.8-1.6,8.8-1.6,3.6-24.4,16-10,16-10,4,1.2,5.2-5.6,5.2-5.6,3.6-10.4,0-24,0-24,3.6-0.4,13.2,5.6,13.2,5.6,2.8-3.6-6.4-20.4-2.4-18s8.4,4,8.4,4c0.8-2-9.2-14.4-9.2-14.4-4.4-2.8-9.6-23.2-9.6-23.2,7.2,3.6-2.8-11.6-2.8-11.6,0-3.2,6-14.4,6-14.4-0.8-6.8,0-6.4,0-6.4,2.8,1.2,10.8,2.8,4-3.6s0.8-11.2,0.8-11.2c4.4-2.8-9.2-2.4-9.2-2.4-5.2-4.4-4.8-8.4-4.8-8.4,8,2-6.4-12.4-8.8-16s7.2-8.8,7.2-8.8c13.2-3.6,1.6-6.8,1.6-6.8-19.6,0.4-8.8-10.4-8.8-10.4,6,0.4,4.4-2,4.4-2-5.2-1.2-14.8-7.6-14.8-7.6-4-3.6-0.4-2.8-0.4-2.8,16.8,1.2-12-10-12-10,8,0-10-10.4-10-10.4-2-1.6-5.2-9.2-5.2-9.2-6-5.2-10.8-12-10.8-12-0.4-4.4-5.2-9.2-5.2-9.2-11.6-13.6-17.2-13.2-17.2-13.2-14.8-3.6-20-2.8-20-2.8l-52.8,4.4c-26.4,12.8-18.6,33.8-18.6,33.8,6.4,8.4,15.6,4.6,15.6,4.6,4.6-6.2,16.2-4,16.2-4,20.401,3.2,17.801-0.4,17.801-0.4-2.4-4.6-18.601-10.8-18.801-11.4s-9-4-9-4c-3-1.2-7.4-10.4-7.4-10.4-3.2-3.4,12.6,2.4,12.6,2.4-1.2,1,6.2,5,6.2,5,17.401-1,28.001,9.8,28.001,9.8,10.799,16.6,10.999,8.4,10.999,8.4,2.8-9.4-9-30.6-9-30.6,0.4-2,8.6,4.6,8.6,4.6,1.4-2,2.2,3.8,2.2,3.8,0.2,2.4,4,10.4,4,10.4,2.8,13,6.4,5.6,6.4,5.6l4.6,9.4c1.4,2.6-4.6,10.2-4.6,10.2-0.2,2.8,0.6,2.6-5,10.2s-2.2,12-2.2,12c-1.4,6.6,7.4,6.2,7.4,6.2,2.6,2.2,6,2.2,6,2.2,1.8,2,4.2,1.4,4.2,1.4,1.6-3.8,7.8-1.8,7.8-1.8,1.4-2.4,9.6-2.8,9.6-2.8,1-2.6,1.4-4.2,4.8-4.8s-21.2-43.6-21.2-43.6c6.4-0.8-1.8-13.2-1.8-13.2-2.2-6.6,9.2,8,11.4,9.4s3.2,3.6,1.6,3.4-3.4,2-2,2.2,14.4,15.2,17.8,25.4,9.4,14.2,15.6,20.2,5.4,30.2,5.4,30.2c-0.4,8.8,5.6,19.4,5.6,19.4,2,3.8-2.2,22-2.2,22-2,2.2-0.6,3-0.6,3,1,1.2,7.8,14.4,7.8,14.4-1.8-0.2,1.8,3.4,1.8,3.4,5.2,6-1.2,3-1.2,3-6-1.6,1,8.2,1,8.2,1.2,1.8-7.8-2.8-7.8-2.8-9.2-0.6,2.4,6.6,2.4,6.6,8.6,7.2-2.8,2.8-2.8,2.8-4.6-1.8-1.4,5-1.4,5,3.2,1.6,20.4,8.6,20.4,8.6,0.4,3.8-2.6,8.8-2.6,8.8,0.4,4-1.8,7.4-1.8,7.4-1.2,8.2-1.8,9-1.8,9-4.2,0.2-11.6,14-11.6,14-1.8,2.6-12,14.6-12,14.6-2,7-20-0.2-20-0.2-6.6,3.4-4.6,0-4.6,0-0.4-2.2,4.4-8.2,4.4-8.2,7-2.6,4.4-13.4,4.4-13.4,4-1.4-7.2-4.2-7-5.4s6-2.6,6-2.6c8-2,3.6-4.4,3.6-4.4-0.6-4,2.4-9.6,2.4-9.6,11.6-0.8,0-17,0-17-10.8-7.6-11.8-13.4-11.8-13.4,12.6-8.2,4.4-20.6,4.6-24.2s1.4-25.2,1.4-25.2c-2-6.2-5-19.8-5-19.8,2.2-5.2,9.6-17.8,9.6-17.8,2.8-4.2,11.6-9,9.4-12s-10-1.2-10-1.2c-7.8-1.4-7.2,3.8-7.2,3.8-1.6,1-2.4,6-2.4,6-0.72,7.933-9.6,14.2-9.6,14.2-11.2,6.2-2,10.2-2,10.2,6,6.6-3.8,6.8-3.8,6.8-11-1.8-2.8,8.4-2.8,8.4,10.8,12.8,7.8,15.6,7.8,15.6-10.2,1,2.4,10.2,2.4,10.2s-0.8-2-0.6-0.2,3.2,6,4,8-3.2,2.2-3.2,2.2c0.6,9.6-14.8,5.4-14.8,5.4l-1.6,0.2c-1.6,0.2-12.8-0.6-18.6-2.8s-12.599-2.2-12.599-2.2-4,1.8-11.601,1.6c-7.6-0.2-15.6,2.6-15.6,2.6-4.4-0.4,4.2-4.8,4.4-4.6s5.8-5.4-2.2-4.8c-21.797,1.635-32.6-8.6-32.6-8.6-2-1.4-4.6-4.2-4.6-4.2-10-2,1.4,12.4,1.4,12.4,1.2,1.4-0.2,2.4-0.2,2.4-0.8-1.6-8.6-7-8.6-7-2.811-0.973-4.174-2.307-6.505-4.793z"/></g><g id="g466" fill="#4c0000"><path id="path468" d="m-3,42.8s11.6,5.6,14.2,8.4,16.6,14.2,16.6,14.2-5.4-2-8-3.8-13.4-10-13.4-10-3.8-6-9.4-8.8z"/></g><g id="g470" fill="#99cc32"><path id="path472" d="M-61.009,11.603c0.337-0.148-0.187-2.86-0.391-3.403-1.022-2.726-10-4.2-10-4.2-0.227,1.365-0.282,2.961-0.176,4.599,0,0,4.868,5.519,10.567,3.004z"/></g><g id="g474" fill="#659900"><path id="path476" d="M-61.009,11.403c-0.449,0.158-0.015-2.734-0.191-3.203-1.022-2.726-10.2-4.3-10.2-4.3-0.227,1.365-0.282,2.961-0.176,4.599,0,0,4.268,5.119,10.567,2.904z"/></g><g id="g478" fill="#000"><path id="path480" d="m-65.4,11.546c-0.625,0-1.131-1.14-1.131-2.546,0-1.405,0.506-2.545,1.131-2.545s1.132,1.14,1.132,2.545c0,1.406-0.507,2.546-1.132,2.546z"/></g><g id="g482" fill="#000"><path id="path484" d="M-65.4,9z"/></g><g id="g486" fill="#000"><path id="path488" d="m-111,109.6s-5.6,10,19.2,4c0,0,14-1.2,16.4-3.6,1.2,0.8,9.566,3.73,12.4,4.4,6.8,1.6,15.2-8.4,15.2-8.4s4.6-10.5,7.4-10.5-0.4,1.6-0.4,1.6-6.6,10.1-6.2,11.7c0,0-5.2,20-21.2,20.8,0,0-16.15,0.95-14.8,6.8,0,0,8.8-2.4,11.2,0,0,0,10.8-0.4,2.8,6l-6.8,11.6s0.14,3.92-10,0.4c-9.8-3.4-20.1-16.3-20.1-16.3s-15.95-14.55-5.1-28.5z"/></g><g id="g490" fill="#e59999"><path id="path492" d="m-112.2,113.6s-2,9.6,34.8-0.8l6.8,0.8c2.4,0.8,14.4,3.6,16.4,2.4,0,0-7.2,13.6-18.8,12,0,0-13.2,1.6-12.8,6.4,0,0,4,7.2,8.8,9.6,0,0,2.8,2.4,2.4,5.6s-3.2,4.8-5.2,5.6-5.2-2.4-6.8-2.4-10-6.4-14.4-11.2-12.8-16.8-12.4-19.6,1.2-8.4,1.2-8.4z"/></g><g id="g494" fill="#b26565"><path id="path496" d="m-109,131.05c2.6,3.95,5.8,8.15,8,10.55,4.4,4.8,12.8,11.2,14.4,11.2s4.8,3.2,6.8,2.4,4.8-2.4,5.2-5.6-2.4-5.6-2.4-5.6c-3.066-1.53-5.806-5.02-7.385-7.35,0,0,0.185,2.55-5.015,1.75s-10.4-3.6-12-6.8-4-5.6-2.4-2,4,7.2,5.6,7.6,1.2,1.6-1.2,1.2-5.2-0.8-9.6-6z"/></g><g id="g498" fill="#992600"><path id="path500" d="m-111.6,110s1.8-13.6,3-17.6c0,0-0.8-6.8,1.6-11s4.4-10.4,7.4-15.8,3.2-9.4,7.2-11,10-10.2,12.8-11.2,2.6-0.2,2.6-0.2,6.8-14.8,20.4-10.8c0,0-16.2-2.8-0.4-12.2,0,0-4.8,1.1-1.5-5.9,2.201-4.668,1.7,2.1-9.3,13.9,0,0-5,8.6-10.2,11.6s-17.2,10-18.4,13.8-4.4,9.6-6.4,11.2-4.8,5.8-5.2,9.2c0,0-1.2,4-2.6,5.2s-1.6,4.4-1.6,6.4-2,4.8-1.8,7.2c0,0,0.8,19,0.4,21l2-3.8z"/></g><g id="g502" fill="#FFF"><path id="path504" d="m-120.2,114.6s-2-1.4-6.4,4.6c0,0,7.3,33,7.3,34.4,0,0,1.1-2.1-0.2-9.3s-2.2-19.9-2.2-19.9l1.5-9.8z"/></g><g id="g506" fill="#992600"><path id="path508" d="m-98.6,54s-17.6,3.2-17.2,32.4l-0.8,24.8s-1.2-25.6-2.4-27.2,2.8-12.8-0.4-6.8c0,0-14,14-6,35.2,0,0,1.5,3.3-1.5-1.3,0,0-4.6-12.6-3.5-19,0,0,0.2-2.2,2.1-5,0,0,8.6-11.7,11.3-14,0,0,1.8-14.4,17.2-19.6,0,0,5.7-2.3,1.2,0.5z"/></g><g id="g510" fill="#000"><path id="path512" d="m40.8-12.2c0.66-0.354,0.651-1.324,1.231-1.497,1.149-0.344,1.313-1.411,1.831-2.195,0.873-1.319,1.066-2.852,1.648-4.343,0.272-0.7,0.299-1.655-0.014-2.315-1.174-2.481-1.876-4.93-3.318-7.356-0.268-0.45-0.53-1.244-0.731-1.842-0.463-1.384-1.72-2.375-2.58-3.695-0.288-0.441,0.237-1.366-0.479-1.45-0.897-0.105-2.346-0.685-2.579,0.341-0.588,2.587,0.423,5.11,1.391,7.552-0.782,0.692-0.448,1.613-0.296,2.38,0.71,3.606-0.488,6.958-1.249,10.432-0.023,0.104,0.319,0.302,0.291,0.364-1.222,2.686-2.674,5.131-4.493,7.512-0.758,0.992-1.63,1.908-2.127,2.971-0.368,0.787-0.776,1.753-0.526,2.741-3.435,2.78-5.685,6.625-8.296,10.471-0.462,0.68-0.171,1.889,0.38,2.158,0.813,0.398,1.769-0.626,2.239-1.472,0.389-0.698,0.742-1.348,1.233-1.991,0.133-0.175-0.046-0.594,0.089-0.715,2.633-2.347,4.302-5.283,6.755-7.651,1.95-0.329,3.487-1.327,5.235-2.34,0.308-0.179,0.832,0.07,1.122-0.125,1.753-1.177,1.751-3.213,1.857-5.123,0.05-0.884,0.246-2.201,1.386-2.812z"/></g><g id="g514" fill="#000"><path id="path516" d="m31.959-16.666c0.124-0.077-0.031-0.5,0.078-0.716,0.162-0.324,0.565-0.512,0.727-0.836,0.109-0.216-0.054-0.596,0.082-0.738,2.333-2.447,2.59-5.471,1.554-8.444,1.024-0.62,1.085-1.882,0.66-2.729-0.853-1.7-1.046-3.626-2.021-5.169-0.802-1.269-2.38-2.513-3.751-1.21-0.421,0.4-0.742,1.187-0.464,1.899,0.064,0.163,0.349,0.309,0.322,0.391-0.107,0.324-0.653,0.548-0.659,0.82-0.03,1.496-0.984,3.007-0.354,4.336,0.772,1.629,1.591,3.486,2.267,5.262-1.234,2.116-0.201,4.565-1.954,6.442-0.136,0.146-0.127,0.532-0.005,0.734,0.292,0.486,0.698,0.892,1.184,1.184,0.202,0.121,0.55,0.123,0.75-0.001,0.578-0.362,0.976-0.849,1.584-1.225z"/></g><g id="g518" fill="#000"><path id="path520" d="m94.771-26.977c1.389,1.792,1.679,4.587-0.37,5.977,0.55,3.309,3.901,1.33,5.999,0.8-0.11-0.388,0.12-0.732,0.4-0.737,1.06-0.015,1.74-1.047,2.8-0.863,0.44-1.557,2.07-2.259,2.72-3.639,1.72-3.695,1.13-7.968-1.45-11.214-0.2-0.254,0.01-0.771-0.11-1.133-0.76-2.211-2.82-2.526-4.76-3.214-1.176-3.875-1.837-7.906-3.599-11.6-1.614-0.25-2.312-1.989-3.649-2.709-1.333-0.719-1.901,0.86-1.86,1.906,0.007,0.205,0.459,0.429,0.289,0.794-0.076,0.164-0.336,0.275-0.336,0.409,0.001,0.135,0.222,0.266,0.356,0.4-0.918,0.82-2.341,1.297-2.636,2.442-0.954,3.71,1.619,6.835,3.287,10.036,0.591,1.135-0.145,2.406-0.905,3.614-0.438,0.695-0.33,1.822-0.054,2.678,0.752,2.331,2.343,4.07,3.878,6.053z"/></g><g id="g522" fill="#000"><path id="path524" d="m57.611-8.591c-1.487,1.851-4.899,4.42-1.982,6.348,0.194,0.129,0.564,0.133,0.737-0.001,2.021-1.565,4.024-2.468,6.46-3.05,0.124-0.029,0.398,0.438,0.767,0.277,1.613-0.703,3.623-0.645,4.807-1.983,3.767,0.224,7.332-0.892,10.723-2.2,1.161-0.448,2.431-1.007,3.632-1.509,1.376-0.576,2.58-1.504,3.692-2.645,0.133-0.136,0.487-0.046,0.754-0.046-0.04-0.863,0.922-0.99,1.169-1.612,0.092-0.232-0.058-0.628,0.075-0.73,2.138-1.63,3.058-3.648,1.889-6.025-0.285-0.578-0.534-1.196-1.1-1.672-1.085-0.911-2.187-0.057-3.234-0.361-0.159,0.628-0.888,0.456-1.274,0.654-0.859,0.439-2.192-0.146-3.051,0.292-1.362,0.695-2.603,0.864-4.025,1.241-0.312,0.082-1.09-0.014-1.25,0.613-0.134-0.134-0.282-0.368-0.388-0.346-1.908,0.396-3.168,0.61-4.469,2.302-0.103,0.133-0.545-0.046-0.704,0.089-0.957,0.808-1.362,2.042-2.463,2.714-0.201,0.123-0.553-0.045-0.747,0.084-0.646,0.431-1.013,1.072-1.655,1.519-0.329,0.229-0.729-0.096-0.697-0.352,0.245-1.947,0.898-3.734,0.323-5.61,2.077-2.52,4.594-4.469,6.4-7.2,0.015-2.166,0.707-4.312,0.594-6.389-0.01-0.193-0.298-0.926-0.424-1.273-0.312-0.854,0.594-1.92-0.25-2.644-1.404-1.203-2.696-0.327-3.52,1.106-1.838,0.39-3.904,1.083-5.482-0.151-1.007-0.787-1.585-1.693-2.384-2.749-0.985-1.302-0.65-2.738-0.58-4.302,0.006-0.128-0.309-0.264-0.309-0.398,0.001-0.135,0.221-0.266,0.355-0.4-0.706-0.626-0.981-1.684-2-2,0.305-1.092-0.371-1.976-1.242-2.278-1.995-0.691-3.672,1.221-5.564,1.294-0.514,0.019-0.981-1.019-1.63-1.344-0.432-0.216-1.136-0.249-1.498,0.017-0.688,0.504-1.277,0.618-2.035,0.823-1.617,0.436-2.895,1.53-4.375,2.385-1.485,0.857-2.44,2.294-3.52,3.614-0.941,1.152-1.077,3.566,0.343,4.066,1.843,0.65,3.147-2.053,5.113-1.727,0.312,0.051,0.518,0.362,0.408,0.75,0.389,0.109,0.607-0.12,0.8-0.4,0.858,1.019,2.022,1.356,2.96,2.229,0.97,0.904,2.716,0.486,3.731,1.483,1.529,1.502,0.97,4.183,2.909,5.488-0.586,1.313-1.193,2.59-1.528,4.017-0.282,1.206,0.712,2.403,1.923,2.312,1.258-0.094,1.52-0.853,2.005-1.929,0.267,0.267,0.736,0.564,0.695,0.78-0.457,2.387-1.484,4.38-1.942,6.811-0.059,0.317-0.364,0.519-0.753,0.409-0.468,4.149-4.52,6.543-7.065,9.708-0.403,0.502-0.407,1.751,0.002,2.154,1.403,1.387,3.363-0.159,5.063-0.662,0.213-1.206,1.072-2.148,2.404-2.092,0.256,0.01,0.491-0.532,0.815-0.662,0.348-0.138,0.85,0.086,1.136-0.112,1.729-1.195,3.137-2.301,4.875-3.49,0.192-0.131,0.536,0.028,0.752-0.08,0.325-0.162,0.512-0.549,0.835-0.734,0.348-0.2,0.59,0.09,0.783,0.37-0.646,0.349-0.65,1.306-1.232,1.508-0.775,0.268-1.336,0.781-2.01,1.228-0.292,0.193-0.951-0.055-1.055,0.124-0.598,1.028-1.782,1.466-2.492,2.349z"/></g><g id="g526" fill="#000"><path id="path528" d="m2.2-58s-9.238-2.872-20.4,22.8c0,0-2.4,5.2-4.8,7.2s-13.6,5.6-15.6,9.6l-10.4,16s14.8-16,18-18.4c0,0,8-8.4,4.8-1.6,0,0-14,10.8-12.8,20,0,0-5.6,14.4-6.4,16.4,0,0,16-32,18.4-33.2s3.6-1.2,2.4,2.4-1.6,20-4.4,22c0,0,8-20.4,7.2-23.6,0,0,3.2-3.6,5.6,1.6l-1.2,16,4.4,12s-2.4-11.2-0.8-26.8c0,0-2-10.4,2-4.8s13.6,11.6,13.6,16.4c0,0-5.2-17.6-14.4-22.4l-4,6-1.2-2s-3.6-0.8,0.8-7.6,4-7.6,4-7.6,6.4,7.2,8,7.2c0,0,13.2-7.6,14.4,16.8,0,0,6.8-14.4-2.4-21.2,0,0-14.8-2-13.6-7.2l7.2-12.4c3.6-5.2,2-2.4,2-2.4z"/></g><g id="g530" fill="#000"><path id="path532" d="m-17.8-41.6-16,5.2-7.2,9.6s17.2-10,21.2-11.2,2-3.6,2-3.6z"/></g><g id="g534" fill="#000"><path id="path536" d="m-57.8-35.2s-2,1.2-2.4,4-2.8,3.2-2,6,2.8,5.2,2.8,1.2,1.6-6,2.4-7.2,2.4-5.6-0.8-4z"/></g><g id="g538" fill="#000"><path id="path540" d="m-66.6,26s-8.4-4-11.6-7.6-2.748,1.566-7.6,1.2c-5.847-0.441-4.8-16.4-4.8-16.4l-4,7.6s-1.2,14.4,6.8,12c3.907-1.172,5.2,0.4,3.6,1.2s5.6,1.2,2.8,2.8,11.6-3.6,9.2,6.8l5.6-7.6z"/></g><g id="g542" fill="#000"><path id="path544" d="m-79.2,40.4s-15.4,4.4-19-5.2c0,0-4.8,2.4-2.6,5.4s3.4,3.4,3.4,3.4,5.4,1.2,4.8,2-3,4.2-3,4.2,10.2-6,16.4-9.8z"/></g><g id="g546" fill="#FFF"><path id="path548" d="m149.2,118.6c-0.43,2.14-2.1,2.94-4,3.6-1.92-0.96-4.51-4.06-6.4-2-0.47-0.48-1.25-0.54-1.6-1.2-0.46-0.9-0.19-1.94-0.53-2.74-0.55-1.28-1.25-2.64-1.07-4.06,1.81-0.71,2.4-2.62,1.93-4.38-0.07-0.26-0.5-0.45-0.3-0.8,0.19-0.33,0.5-0.55,0.77-0.82-0.13,0.14-0.28,0.37-0.39,0.35-0.61-0.11-0.49-0.75-0.36-1.13,0.59-1.75,2.6-2.01,3.95-0.82,0.26-0.56,0.77-0.37,1.2-0.4-0.05-0.58,0.36-1.11,0.56-1.53,0.52-1.09,2.14,0.01,2.94-0.6,1.08-0.83,2.14-1.52,3.22-0.92,1.81,1.01,3.52,2.22,4.72,3.97,0.57,0.83,0.81,2.11,0.75,3.07-0.04,0.65-1.42,0.29-1.76,1.22-0.65,1.75,1.19,2.27,1.94,3.61,0.2,0.35-0.06,0.65-0.38,0.75-0.41,0.13-1.19-0.06-1.06,0.39,0.98,3.19-1.78,3.87-4.13,4.44z"/></g><g id="g550" fill="#FFF"><path id="path552" d="m139.6,138.2c-0.01-1.74-1.61-3.49-0.4-5.2,0.14,0.14,0.27,0.36,0.4,0.36,0.14,0,0.27-0.22,0.4-0.36,1.5,2.22,5.15,3.14,5.01,5.99-0.03,0.45-1.11,1.37-0.21,2.01-1.81,1.35-1.87,3.72-2.8,5.6-1.24-0.28-2.45-0.65-3.6-1.2,0.35-1.48,0.24-3.17,1.06-4.49,0.43-0.7,0.14-1.78,0.14-2.71z"/></g><g id="g554" fill="#CCC"><path id="path556" d="m-26.6,129.2s-16.858,10.14-2.8-5.2c8.8-9.6,18.8-15.2,18.8-15.2s10.4-4.4,14-5.6,18.8-6.4,22-6.8,12.8-4.4,19.6-0.4,14.8,8.4,14.8,8.4-16.4-8.4-20-6-10.8,2-16.8,5.2c0,0-14.8,4.4-18,6.4s-13.6,13.6-15.2,12.8,0.4-1.2,1.6-4-0.8-4.4-8.8,2-9.2,8.4-9.2,8.4z"/></g><g id="g558" fill="#000"><path id="path560" d="m-19.195,123.23s1.41-13.04,9.888-11.37c0,0,8.226-4.17,10.948-6.14,0,0,8.139-1.7,9.449-2.32,18.479-8.698,33.198-4.179,33.745-5.299,0.546-1.119,20.171,5.999,23.78,10.079,0.391,0.45-10.231-5.59-19.929-7.48-8.273-1.617-29.875,0.24-40.781,5.78-2.973,1.51-11.918,7.29-14.449,7.18s-12.651,9.57-12.651,9.57z"/></g><g id="g562" fill="#CCC"><path id="path564" d="m-23,148.8s-15.2-2.4,1.6-4c0,0,18-2,22-7.2,0,0,13.6-9.2,16.4-9.6s32.8-7.6,33.2-10,6-2.4,7.6-1.6,0.8,2-2,2.8-34,17.2-40.4,18.4-18,8.8-22.8,10-15.6,1.2-15.6,1.2z"/></g><g id="g566" fill="#000"><path id="path568" d="m-3.48,141.4s-8.582-0.83,0.019-1.64c0,0,8.816-3.43,10.864-6.09,0,0,6.964-4.71,8.397-4.92,1.434-0.2,15.394-3.89,15.599-5.12s34.271-13.81,38.691-10.62c2.911,2.1-6.99,0.43-16.624,4.84-1.355,0.62-35.208,15.2-38.485,15.82-3.277,0.61-9.216,4.5-11.674,5.12-2.457,0.61-6.787,2.61-6.787,2.61z"/></g><g id="g570" fill="#000"><path id="path572" d="m-11.4,143.6s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/></g><g id="g574" fill="#000"><path id="path576" d="m-18.6,145.2s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/></g><g id="g578" fill="#000"><path id="path580" d="m-29,146.8s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/></g><g id="g582" fill="#000"><path id="path584" d="m-36.6,147.6s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/></g><g id="g586" fill="#000"><path id="path588" d="m1.8,108,3.2,1.6c-1.2,1.6-4.4,1.2-4.4,1.2l1.2-2.8z"/></g><g id="g590" fill="#000"><path id="path592" d="m-8.2,113.6s6.506-2.14,4,1.2c-1.2,1.6-3.6,0.8-3.6,0.8l-0.4-2z"/></g><g id="g594" fill="#000"><path id="path596" d="m-19.4,118.4s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/></g><g id="g598" fill="#000"><path id="path600" d="m-27,124.4s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/></g><g id="g602" fill="#000"><path id="path604" d="m-33.8,129.2s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/></g><g id="g606" fill="#000"><path id="path608" d="m5.282,135.6s6.921-0.53,5.324,1.6c-1.597,2.12-4.792,1.06-4.792,1.06l-0.532-2.66z"/></g><g id="g610" fill="#000"><path id="path612" d="m15.682,130.8s6.921-0.53,5.324,1.6c-1.597,2.12-4.792,1.06-4.792,1.06l-0.532-2.66z"/></g><g id="g614" fill="#000"><path id="path616" d="m26.482,126.4s6.921-0.53,5.324,1.6c-1.597,2.12-4.792,1.06-4.792,1.06l-0.532-2.66z"/></g><g id="g618" fill="#000"><path id="path620" d="m36.882,121.6s6.921-0.53,5.324,1.6c-1.597,2.12-4.792,1.06-4.792,1.06l-0.532-2.66z"/></g><g id="g622" fill="#000"><path id="path624" d="m9.282,103.6s6.921-0.53,5.324,1.6c-1.597,2.12-5.592,1.86-5.592,1.86l0.268-3.46z"/></g><g id="g626" fill="#000"><path id="path628" d="m19.282,100.4s6.921-0.534,5.324,1.6c-1.597,2.12-5.992,1.86-5.992,1.86l0.668-3.46z"/></g><g id="g630" fill="#000"><path id="path632" d="m-3.4,140.4s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/></g><g id="g634" fill="#992600"><path id="path636" d="m-76.6,41.2s-4.4,8.8-4.8,12c0,0,0.8-8.8,2-10.8s2.8-1.2,2.8-1.2z"/></g><g id="g638" fill="#992600"><path id="path640" d="m-95,55.2s-3.2,14.4-2.8,17.2c0,0-1.2-11.6-0.8-12.8s3.6-4.4,3.6-4.4z"/></g><g id="g642" fill="#CCC"><path id="path644" d="m-74.2-19.4-0.2,3.2-2.2,0.2s14.2,12.6,14.8,20.2c0,0,0.8-8.2-12.4-23.6z"/></g><g id="g646" fill="#000"><path id="path648" d="m-70.216-18.135c-0.431-0.416-0.212-1.161-0.62-1.421-0.809-0.516,1.298-0.573,1.07-1.289-0.383-1.206-0.196-1.227-0.318-2.503-0.057-0.598,0.531-2.138,0.916-2.578,1.446-1.652,0.122-4.584,1.762-6.135,0.304-0.289,0.68-0.841,0.965-1.259,0.659-0.963,1.843-1.451,2.793-2.279,0.318-0.276,0.117-1.103,0.686-1.011,0.714,0.115,1.955-0.015,1.91,0.826-0.113,2.12-1.442,3.84-2.722,5.508,0.451,0.704-0.007,1.339-0.291,1.896-1.335,2.62-1.146,5.461-1.32,8.301-0.005,0.085-0.312,0.163-0.304,0.216,0.353,2.335,0.937,4.534,1.816,6.763,0.366,0.93,0.837,1.825,0.987,2.752,0.111,0.686,0.214,1.519-0.194,2.224,2.035,2.89,0.726,5.541,1.895,9.072,0.207,0.625,1.899,2.539,1.436,2.378-2.513-0.871-2.625-1.269-2.802-2.022-0.146-0.623-0.476-2-0.713-2.602-0.064-0.164-0.235-2.048-0.313-2.17-1.513-2.382-0.155-2.206-1.525-4.564-1.428-0.68-2.394-1.784-3.517-2.946-0.198-0.204,0.945-0.928,0.764-1.141-1.092-1.289-2.245-2.056-1.909-3.549,0.155-0.69,0.292-1.747-0.452-2.467z"/></g><g id="g650" fill="#000"><path id="path652" d="m-73.8-16.4s0.4,6.8,2.8,8.4,1.2,0.8-2-0.4-2-2-2-2-2.8,0.4-0.4,2.4,6,4.4,4.4,4.4-9.2-4-9.2-6.8-1-6.9-1-6.9,1.1-0.8,5.9-0.7c0,0,1.4,0.7,1.5,1.6z"/></g><g id="g654" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path656" d="m-74.6,2.2s-8.52-2.791-27,0.6c0,0,9.031-2.078,27.8,0.2,10.3,1.25-0.8-0.8-0.8-0.8z"/></g><g id="g658" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path660" d="m-72.502,2.129s-8.246-3.518-26.951-1.737c0,0,9.178-1.289,27.679,2.603,10.154,2.136-0.728-0.866-0.728-0.866z"/></g><g id="g662" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path664" d="m-70.714,2.222s-7.962-4.121-26.747-3.736c0,0,9.248-0.604,27.409,4.654,9.966,2.885-0.662-0.918-0.662-0.918z"/></g><g id="g666" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path668" d="m-69.444,2.445s-6.824-4.307-23.698-5.405c0,0,8.339,0.17,24.22,6.279,8.716,3.353-0.522-0.874-0.522-0.874z"/></g><g id="g670" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path672" d="m45.84,12.961s-0.93,0.644-0.716-0.537c0.215-1.181,28.423-14.351,32.037-14.101,0,0-30.248,13.206-31.321,14.638z"/></g><g id="g674" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path676" d="m42.446,13.6s-0.876,0.715-0.755-0.479,27.208-16.539,30.83-16.573c0,0-29.117,15.541-30.075,17.052z"/></g><g id="g678" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path680" d="m39.16,14.975s-0.828,0.772-0.786-0.428c0.042-1.199,19.859-16.696,29.671-18.57,0,0-18.03,8.127-28.885,18.998z"/></g><g id="g682" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path684" d="m36.284,16.838s-0.745,0.694-0.707-0.385c0.038-1.08,17.872-15.027,26.703-16.713,0,0-16.226,7.314-25.996,17.098z"/></g><g id="g686" fill="#CCC"><path id="path688" d="m4.6,164.8s-15.2-2.4,1.6-4c0,0,18-2,22-7.2,0,0,13.6-9.2,16.4-9.6s19.2-4,19.6-6.4,6.4-4.8,8-4,1.6,10-1.2,10.8-21.6,8-28,9.2-18,8.8-22.8,10-15.6,1.2-15.6,1.2z"/></g><g id="g690" fill="#000"><path id="path692" d="m77.6,127.4s-3,1.6-4.2,4.2c0,0-6.4,10.6-20.6,13.8,0,0-23,9-30.8,11,0,0-13.4,5-20.8,4.2,0,0-7,0.2-0.8,1.8,0,0,20.2-2,23.6-3.8,0,0,15.6-5.2,18.6-7.8s21.2-7.6,23.4-9.6,12-10.4,11.6-13.8z"/></g><g id="g694" fill="#000"><path id="path696" d="m18.882,158.91s5.229-0.23,4.076,1.32-3.601,0.68-3.601,0.68l-0.475-2z"/></g><g id="g698" fill="#000"><path id="path700" d="m11.68,160.26s5.228-0.22,4.076,1.33c-1.153,1.55-3.601,0.67-3.601,0.67l-0.475-2z"/></g><g id="g702" fill="#000"><path id="path704" d="m1.251,161.51s5.229-0.23,4.076,1.32-3.601,0.68-3.601,0.68l-0.475-2z"/></g><g id="g706" fill="#000"><path id="path708" d="m-6.383,162.06s5.229-0.23,4.076,1.32-3.601,0.67-3.601,0.67l-0.475-1.99z"/></g><g id="g710" fill="#000"><path id="path712" d="m35.415,151.51s6.96-0.3,5.425,1.76c-1.534,2.07-4.793,0.9-4.793,0.9l-0.632-2.66z"/></g><g id="g714" fill="#000"><path id="path716" d="m45.73,147.09s5.959-3.3,5.425,1.76c-0.27,2.55-4.793,0.9-4.793,0.9l-0.632-2.66z"/></g><g id="g718" fill="#000"><path id="path720" d="m54.862,144.27s7.159-3.7,5.425,1.77c-0.778,2.44-4.794,0.9-4.794,0.9l-0.631-2.67z"/></g><g id="g722" fill="#000"><path id="path724" d="m64.376,139.45s4.359-4.9,5.425,1.76c0.406,2.54-4.793,0.9-4.793,0.9l-0.632-2.66z"/></g><g id="g726" fill="#000"><path id="path728" d="m26.834,156s5.228-0.23,4.076,1.32c-1.153,1.55-3.602,0.68-3.602,0.68l-0.474-2z"/></g><g id="g730" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path732" d="m62.434,34.603s-0.726,0.665-0.727-0.406c0-1.07,17.484-14.334,26.327-15.718,0,0-16.099,6.729-25.6,16.124z"/></g><g id="g734" fill="#000"><path id="path736" d="m65.4,98.4s22.001,22.4,31.201,26c0,0,9.199,11.2,5.199,37.2,0,0-3.199,7.6-6.399-13.2,0,0,3.2-25.2-8-9.2,0,0-8.401-9.9-2.001-9.6,0,0,3.201,2,3.601,0.4s-7.601-15.2-24.801-29.6,1.2-2,1.2-2z"/></g><g id="g738" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path740" d="m7,137.2s-0.2-1.8,1.6-1,96,7,127.6,31c0,0-45.199-23.2-129.2-30z"/></g><g id="g742" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path744" d="m17.4,132.8s-0.2-1.8,1.6-1,138.4-0.2,162,32.2c0,0-22-25.2-163.6-31.2z"/></g><g id="g746" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path748" d="m29,128.8s-0.2-1.8,1.6-1,175.2-12.2,198.8,20.2c0,0-9.6-25.6-200.4-19.2z"/></g><g id="g750" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path752" d="m39,124s-0.2-1.8,1.6-1,124-37.8,147.6-5.4c0,0-13.4-24.6-149.2,6.4z"/></g><g id="g754" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path756" d="m-19,146.8s-0.2-1.8,1.6-1,19.6,3,21.6,41.8c0,0-7.2-42-23.2-40.8z"/></g><g id="g758" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path760" d="m-27.8,148.4s-0.2-1.8,1.6-1,16-3.8,13.2,35c0,0,1.2-35.2-14.8-34z"/></g><g id="g762" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path764" d="m-35.8,148.8s-0.2-1.8,1.6-1,17.2,1.4,4.8,23.8c0,0,9.6-24-6.4-22.8z"/></g><g id="g766" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path768" d="m11.526,104.46s-0.444,2,1.105,0.79c16.068-12.628,48.51-71.53,104.2-77.164,0,0-38.312-12.11-105.3,76.374z"/></g><g id="g770" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path772" d="m22.726,102.66s-1.363-1.19,0.505-1.81c1.868-0.63,114.31-73.13,153.6-65.164,0,0-27.11-7.51-154.1,66.974z"/></g><g id="g774" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path776" d="m1.885,108.77s-0.509,1.6,1.202,0.62c8.975-5.12,12.59-62.331,56.167-63.586,0,0-32.411-14.714-57.369,62.966z"/></g><g id="g778" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path780" d="m-18.038,119.79s-1.077,1.29,0.876,1.03c10.246-1.33,31.651-42.598,76.09-37.519,0,0-31.966-14.346-76.966,36.489z"/></g><g id="g782" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path784" d="m-6.8,113.67s-0.811,1.47,1.058,0.84c9.799-3.27,22.883-47.885,67.471-51.432,0,0-34.126-7.943-68.529,50.592z"/></g><g id="g786" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path788" d="m-25.078,124.91s-0.873,1.04,0.709,0.84c8.299-1.08,25.637-34.51,61.633-30.396,0,0-25.893-11.62-62.342,29.556z"/></g><g id="g790" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path792" d="m-32.677,130.82s-1.005,1.05,0.586,0.93c4.168-0.31,34.806-33.39,53.274-17.89,0,0-12.015-18.721-53.86,16.96z"/></g><g id="g794" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path796" d="m36.855,98.898s-1.201-1.355,0.731-1.74c1.932-0.384,122.63-58.097,160.59-45.231,0,0-25.94-10.874-161.32,46.971z"/></g><g id="g798" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path800" d="m3.4,163.2s-0.2-1.8,1.6-1,17.2,1.4,4.8,23.8c0,0,9.6-24-6.4-22.8z"/></g><g id="g802" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path804" d="m13.8,161.6s-0.2-1.8,1.6-1,19.6,3,21.6,41.8c0,0-7.2-42-23.2-40.8z"/></g><g id="g806" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path808" d="m20.6,160s-0.2-1.8,1.6-1,26.4,4.2,50,36.6c0,0-35.6-36.8-51.6-35.6z"/></g><g id="g810" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path812" d="m28.225,157.97s-0.437-1.76,1.453-1.2c1.89,0.55,22.324-1.35,60.421,32.83,0,0-46.175-34.94-61.874-31.63z"/></g><g id="g814" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path816" d="m38.625,153.57s-0.437-1.76,1.453-1.2c1.89,0.55,36.724,5.05,88.422,40.03,0,0-74.176-42.14-89.875-38.83z"/></g><g id="g818" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path820" d="m-1.8,142s-0.2-1.8,1.6-1,55.2,3.4,85.6,30.2c0,0-34.901-24.77-87.2-29.2z"/></g><g id="g822" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path824" d="m-11.8,146s-0.2-1.8,1.6-1,26.4,4.2,50,36.6c0,0-35.6-36.8-51.6-35.6z"/></g><g id="g826" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path828" d="m49.503,148.96s-0.565-1.72,1.361-1.3c1.926,0.41,36.996,2.34,91.116,33.44,0,0-77.663-34.4-92.477-32.14z"/></g><g id="g830" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path832" d="m57.903,146.56s-0.565-1.72,1.361-1.3c1.926,0.41,36.996,2.34,91.116,33.44,0,0-77.063-34.8-92.477-32.14z"/></g><g id="g834" fill="#FFF" stroke="#000" stroke-width=".1"><path id="path836" d="m67.503,141.56s-0.565-1.72,1.361-1.3c1.926,0.41,44.996,4.74,134.72,39.04,0,0-120.66-40.4-136.08-37.74z"/></g><g id="g838" fill="#000"><path id="path840" d="m-43.8,148.4s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/></g><g id="g842" fill="#000"><path id="path844" d="m-13,162.4s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/></g><g id="g846" fill="#000"><path id="path848" d="m-21.8,162s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/></g><g id="g850" fill="#000"><path id="path852" d="m-117.17,150.18s5.05,1.32,3.39,2.44-3.67-0.42-3.67-0.42l0.28-2.02z"/></g><g id="g854" fill="#000"><path id="path856" d="m-115.17,140.58s5.05,1.32,3.39,2.44-3.67-0.42-3.67-0.42l0.28-2.02z"/></g><g id="g858" fill="#000"><path id="path860" d="m-122.37,136.18s5.05,1.32,3.39,2.44-3.67-0.42-3.67-0.42l0.28-2.02z"/></g><g id="g862" fill="#CCC"><path id="path864" d="m-42.6,211.2-5.6,2c-2,0-13.2,3.6-18.8,13.6,0,0,12.4-9.6,24.4-15.6z"/></g><g id="g866" fill="#CCC"><path id="path868" d="m45.116,303.85c0.141,0.25,0.196,0.67,0.488,0.69,0.658,0.04,1.891,0.34,1.766-0.29-0.848-4.31-1.722-9.25-5.855-11.05-0.639-0.28-2.081,0.13-2.155,1.02-0.127,1.52-0.244,2.87,0.065,4.33,0.3,1.43,2.458,1.43,3.375,0.05,0.936,1.67,1.368,3.52,2.316,5.25z"/></g><g id="g870" fill="#CCC"><path id="path872" d="m34.038,308.58c0.748,1.41,0.621,3.27,2.036,3.84,0.74,0.29,2.59-0.68,2.172-1.76-0.802-2.06-1.19-4.3-2.579-6.11-0.2-0.26,0.04-0.79-0.12-1.12-0.594-1.22-1.739-1.96-3.147-1.63-1.115,2.2,0.033,4.33,1.555,6.04,0.136,0.15-0.03,0.53,0.083,0.74z"/></g><g id="g874" fill="#CCC"><path id="path876" d="m-5.564,303.39c-0.108-0.38-0.146-0.84,0.019-1.16,0.531-1.03,1.324-2.15,0.987-3.18-0.348-1.05-1.464-0.87-2.114-0.3-1.135,0.99-1.184,2.82-1.875,4.18-0.196,0.38-0.145,0.96-0.586,1.35-0.474,0.42-0.914,1.94-0.818,2.51,0.053,0.32-0.13,10.22,0.092,9.96,0.619-0.73,3.669-10.47,3.738-11.36,0.057-0.73,0.789-1.19,0.557-2z"/></g><g id="g878" fill="#CCC"><path id="path880" d="m-31.202,296.6c2.634-2.5,5.424-5.46,4.982-9.17-0.116-0.98-1.891-0.45-2.078,0.39-0.802,3.63-2.841,6.29-5.409,8.68-2.196,2.05-4.058,8.39-4.293,8.9,3.697-5.26,5.954-8,6.798-8.8z"/></g><g id="g882" fill="#CCC"><path id="path884" d="m-44.776,290.64c0.523-0.38,0.221-0.87,0.438-1.2,0.953-1.46,2.254-2.7,2.272-4.44,0.003-0.28-0.375-0.59-0.71-0.36-0.277,0.18-0.619,0.31-0.727,0.44-2.03,2.45-3.43,5.12-4.873,7.93-0.183,0.36-1.327,4.85-1.014,4.96,0.239,0.09,1.959-4.09,2.169-4.21,1.263-0.68,1.275-2.3,2.445-3.12z"/></g><g id="g886" fill="#CCC"><path id="path888" d="m-28.043,310.18c0.444-0.87,2.02-2.07,1.907-2.96-0.118-0.93,0.35-2.37-0.562-1.68-1.257,0.94-4.706,2.29-4.976,8.1-0.026,0.57,2.948-2.12,3.631-3.46z"/></g><g id="g890" fill="#CCC"><path id="path892" d="m-13.6,293c0.4-0.67,1.108-0.19,1.567-0.46,0.648-0.37,1.259-0.93,1.551-1.58,0.97-2.14,2.739-3.96,2.882-6.36-1.491-1.4-2.17,0.64-2.8,1.6-1.323-1.65-2.322,0.23-3.622,0.75-0.07,0.03-0.283-0.32-0.358-0.29-1.177,0.44-1.857,1.52-2.855,2.3-0.171,0.13-0.576-0.05-0.723,0.09-0.652,0.6-1.625,0.93-1.905,1.61-1.11,2.7-4.25,4.8-6.137,12.34,0.381,0.91,4.512-6.64,4.999-7.34,0.836-1.2,0.954,1.66,2.23,1,0.051-0.03,0.237,0.21,0.371,0.34,0.194-0.28,0.412-0.51,0.8-0.4,0-0.4-0.134-0.96,0.067-1.11,1.237-0.98,1.153-2.05,1.933-3.29,0.458,0.79,1.519,0.07,2,0.8z"/></g><g id="g894" fill="#CCC"><path id="path896" d="m46.2,347.4s7.4-20.4,3-31.6c0,0,11.4,21.6,6.8,32.8,0,0-0.4-10.4-4.4-15.4,0,0-4,12.8-5.4,14.2z"/></g><g id="g898" fill="#CCC"><path id="path900" d="m31.4,344.8s5.4-8.8-2.6-27.2c0,0-0.8,20.4-7.6,31.4,0,0,14.2-20.2,10.2-4.2z"/></g><g id="g902" fill="#CCC"><path id="path904" d="m21.4,342.8s-0.2-20,0.2-23c0,0-3.8,16.6-14,26.2,0,0,14.4-12,13.8-3.2z"/></g><g id="g906" fill="#CCC"><path id="path908" d="m11.8,310.8s6,13.6-4,32c0,0,6.4-12.2,1.6-19.2,0,0,2.6-3.4,2.4-12.8z"/></g><g id="g910" fill="#CCC"><path id="path912" d="m-7.4,342.4s-1-15.6,0.8-17.8c0,0,0.2-6.4-0.2-7.4,0,0,4-6.2,4.2,1.2,0,0,1.4,7.8,4.2,12.4,0,0,3.6,5.4,3.4,11.8,0,0-10-30.2-12.4-0.2z"/></g><g id="g914" fill="#CCC"><path id="path916" d="m-11,314.8s-6.6,10.8-8.4,29.8c0,0-1.4-6.2,2.4-20.6,0,0,4.2-15.4,6-9.2z"/></g><g id="g918" fill="#CCC"><path id="path920" d="m-32.8,334.6s5-5.4,6.4-10.4c0,0,3.6-15.8-2.8-7.2,0,0,0.2,8-8,15.4,0,0,4.8-2.4,4.4,2.2z"/></g><g id="g922" fill="#CCC"><path id="path924" d="m-38.6,329.6s3.4-17.4,4.2-18.2c0,0,1.8-3.4-1-0.2,0,0-8.8,19.2-12.8,25.8,0,0,8-9.2,9.6-7.4z"/></g><g id="g926" fill="#CCC"><path id="path928" d="m-44.4,313s11.6-22.4-10.2,3.4c0,0,11-9.8,10.2-3.4z"/></g><g id="g930" fill="#CCC"><path id="path932" d="m-59.8,298.4s4.8-18.8,7.4-18.6l1.6,1.6s-6,9.6-5.4,19.4c0,0-0.6-9.6-3.6-2.4z"/></g><g id="g934" fill="#CCC"><path id="path936" d="m270.5,287s-12-10-14.5-13.5c0,0,13.5,18.5,13.5,25.5,0,0,2.5-7.5,1-12z"/></g><g id="g938" fill="#CCC"><path id="path940" d="m276,265s-21-15-24.5-22.5c0,0,26.5,29.5,26.5,34,0,0,0.5-9-2-11.5z"/></g><g id="g942" fill="#CCC"><path id="path944" d="m293,111s-12-8-13.5-6c0,0,10.5,6.5,13,15,0,0-1.5-9,0.5-9z"/></g><g id="g946" fill="#CCC"><path id="path948" d="m301.5,191.5-17.5-12s19,17,19.5,21l-2-9z"/></g><g id="g950" stroke="#000"><path id="path952" d="m-89.25,169,22,4.75"/></g><g id="g954" stroke="#000"><path id="path956" d="m-39,331s-0.5-3.5-9.5,7"/></g><g id="g958" stroke="#000"><path id="path960" d="m-33.5,336s2-6.5-4.5-2"/></g><g id="g962" stroke="#000"><path id="path964" d="m20.5,344.5s1.5-11-10,2"/></g></g></svg>
\ No newline at end of file diff --git a/examples/todos/Cargo.toml b/examples/todos/Cargo.toml index c8926c33..5781ddef 100644 --- a/examples/todos/Cargo.toml +++ b/examples/todos/Cargo.toml @@ -2,7 +2,7 @@ name = "todos" version = "0.1.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" publish = false [dependencies] diff --git a/examples/todos/index.html b/examples/todos/index.html new file mode 100644 index 00000000..ee5570fb --- /dev/null +++ b/examples/todos/index.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8" content="text/html; charset=utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <title>Todos - Iced</title> + <base data-trunk-public-url /> +</head> +<body> +<link data-trunk rel="rust" href="Cargo.toml" data-wasm-opt="z" data-bin="todos" /> +</body> +</html> diff --git a/examples/tooltip/Cargo.toml b/examples/tooltip/Cargo.toml index 1171de00..25840fb4 100644 --- a/examples/tooltip/Cargo.toml +++ b/examples/tooltip/Cargo.toml @@ -2,7 +2,7 @@ name = "tooltip" version = "0.1.0" authors = ["Yusuf Bera Ertan <y.bera003.06@protonmail.com>"] -edition = "2018" +edition = "2021" publish = false [dependencies] diff --git a/examples/tour/Cargo.toml b/examples/tour/Cargo.toml index bc7fac11..39e83671 100644 --- a/examples/tour/Cargo.toml +++ b/examples/tour/Cargo.toml @@ -2,7 +2,7 @@ name = "tour" version = "0.1.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" publish = false [dependencies] diff --git a/examples/tour/README.md b/examples/tour/README.md index f380931a..e7cd2d5c 100644 --- a/examples/tour/README.md +++ b/examples/tour/README.md @@ -14,7 +14,7 @@ The __[`main`]__ file contains all the code of the example! All the cross-platfo [`iced_winit`]: ../../winit [`iced_native`]: ../../native [`iced_wgpu`]: ../../wgpu -[`iced_web`]: ../../web +[`iced_web`]: https://github.com/iced-rs/iced_web [`winit`]: https://github.com/rust-windowing/winit [`wgpu`]: https://github.com/gfx-rs/wgpu-rs @@ -25,4 +25,4 @@ cargo run --package tour The web version can be run by following [the usage instructions of `iced_web`] or by accessing [iced.rs](https://iced.rs/)! -[the usage instructions of `iced_web`]: ../../web#usage +[the usage instructions of `iced_web`]: https://github.com/iced-rs/iced_web#usage diff --git a/examples/tour/images/ferris.png b/examples/tour/images/ferris.png Binary files differindex ebce1a14..9e883834 100644 --- a/examples/tour/images/ferris.png +++ b/examples/tour/images/ferris.png diff --git a/examples/tour/index.html b/examples/tour/index.html new file mode 100644 index 00000000..c64af912 --- /dev/null +++ b/examples/tour/index.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8" content="text/html; charset=utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <title>Tour - Iced</title> + <base data-trunk-public-url /> +</head> +<body> +<link data-trunk rel="rust" href="Cargo.toml" data-wasm-opt="z" data-bin="tour" /> +</body> +</html> diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index d4b41310..e199c88c 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -1,7 +1,7 @@ use iced::{ alignment, button, scrollable, slider, text_input, Button, Checkbox, Color, - Column, Container, Element, Image, Length, Radio, Row, Sandbox, Scrollable, - Settings, Slider, Space, Text, TextInput, Toggler, + Column, Container, ContentFit, Element, Image, Length, Radio, Row, Sandbox, + Scrollable, Settings, Slider, Space, Text, TextInput, Toggler, }; pub fn main() -> iced::Result { @@ -139,7 +139,8 @@ impl Steps { can_continue: false, }, Step::Image { - width: 300, + height: 200, + current_fit: ContentFit::Contain, slider: slider::State::new(), }, Step::Scrollable, @@ -213,8 +214,9 @@ enum Step { can_continue: bool, }, Image { - width: u16, + height: u16, slider: slider::State, + current_fit: ContentFit, }, Scrollable, TextInput { @@ -234,7 +236,8 @@ pub enum StepMessage { TextSizeChanged(u16), TextColorChanged(Color), LanguageSelected(Language), - ImageWidthChanged(u16), + ImageHeightChanged(u16), + ImageFitSelected(ContentFit), InputChanged(String), ToggleSecureInput(bool), DebugToggled(bool), @@ -279,9 +282,14 @@ impl<'a> Step { *spacing = new_spacing; } } - StepMessage::ImageWidthChanged(new_width) => { - if let Step::Image { width, .. } = self { - *width = new_width; + StepMessage::ImageHeightChanged(new_height) => { + if let Step::Image { height, .. } = self { + *height = new_height; + } + } + StepMessage::ImageFitSelected(fit) => { + if let Step::Image { current_fit, .. } = self { + *current_fit = fit; } } StepMessage::InputChanged(new_value) => { @@ -346,7 +354,11 @@ impl<'a> Step { color_sliders, color, } => Self::text(size_slider, *size, color_sliders, *color), - Step::Image { width, slider } => Self::image(*width, slider), + Step::Image { + height, + slider, + current_fit, + } => Self::image(*height, slider, *current_fit), Step::RowsAndColumns { layout, spacing_slider, @@ -574,23 +586,44 @@ impl<'a> Step { } fn image( - width: u16, + height: u16, slider: &'a mut slider::State, + current_fit: ContentFit, ) -> Column<'a, StepMessage> { + const FIT_MODES: [(ContentFit, &str); 3] = [ + (ContentFit::Contain, "Contain"), + (ContentFit::Cover, "Cover"), + (ContentFit::Fill, "Fill"), + ]; + + let mode_selector = FIT_MODES.iter().fold( + Column::new().padding(10).spacing(20), + |choices, (mode, name)| { + choices.push(Radio::new( + *mode, + *name, + Some(current_fit), + StepMessage::ImageFitSelected, + )) + }, + ); + Self::container("Image") - .push(Text::new("An image that tries to keep its aspect ratio.")) - .push(ferris(width)) + .push(Text::new("Pictures of things in all shapes and sizes!")) + .push(ferris(height, current_fit)) .push(Slider::new( slider, - 100..=500, - width, - StepMessage::ImageWidthChanged, + 50..=500, + height, + StepMessage::ImageHeightChanged, )) .push( - Text::new(format!("Width: {} px", width.to_string())) + Text::new(format!("Height: {} px", height)) .width(Length::Fill) .horizontal_alignment(alignment::Horizontal::Center), ) + .push(Text::new("Pick a content fit strategy:")) + .push(mode_selector) } fn scrollable() -> Column<'a, StepMessage> { @@ -613,7 +646,7 @@ impl<'a> Step { .horizontal_alignment(alignment::Horizontal::Center), ) .push(Column::new().height(Length::Units(4096))) - .push(ferris(300)) + .push(ferris(200, ContentFit::Contain)) .push( Text::new("You made it!") .width(Length::Fill) @@ -699,7 +732,10 @@ impl<'a> Step { } } -fn ferris<'a>(width: u16) -> Container<'a, StepMessage> { +fn ferris<'a>( + height: u16, + content_fit: ContentFit, +) -> Container<'a, StepMessage> { Container::new( // This should go away once we unify resource loading on native // platforms @@ -708,10 +744,11 @@ fn ferris<'a>(width: u16) -> Container<'a, StepMessage> { } else { Image::new(format!( "{}/images/ferris.png", - env!("CARGO_MANIFEST_DIR") + env!("CARGO_MANIFEST_DIR"), )) } - .width(Length::Units(width)), + .height(Length::Units(height)) + .content_fit(content_fit), ) .width(Length::Fill) .center_x() @@ -783,7 +820,8 @@ pub enum Layout { } mod style { - use iced::{button, Background, Color, Vector}; + use iced::button; + use iced::{Background, Color, Vector}; pub enum Button { Primary, diff --git a/examples/url_handler/Cargo.toml b/examples/url_handler/Cargo.toml index 911b2f25..63c7ec27 100644 --- a/examples/url_handler/Cargo.toml +++ b/examples/url_handler/Cargo.toml @@ -2,9 +2,9 @@ name = "url_handler" version = "0.1.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" publish = false [dependencies] iced = { path = "../.." } -iced_native = { path = "../../native" }
\ No newline at end of file +iced_native = { path = "../../native" } diff --git a/examples/websocket/Cargo.toml b/examples/websocket/Cargo.toml index 6b4d9d10..db131dd7 100644 --- a/examples/websocket/Cargo.toml +++ b/examples/websocket/Cargo.toml @@ -2,7 +2,7 @@ name = "websocket" version = "0.1.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" publish = false [dependencies] diff --git a/futures/Cargo.toml b/futures/Cargo.toml index aa55df1e..78e673e0 100644 --- a/futures/Cargo.toml +++ b/futures/Cargo.toml @@ -2,7 +2,7 @@ name = "iced_futures" version = "0.3.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" description = "Commands, subscriptions, and runtimes for Iced" license = "MIT" repository = "https://github.com/iced-rs/iced" @@ -36,6 +36,7 @@ optional = true [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen-futures = "0.4" +wasm-timer = "0.2" [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] diff --git a/futures/src/backend.rs b/futures/src/backend.rs new file mode 100644 index 00000000..1cc4af80 --- /dev/null +++ b/futures/src/backend.rs @@ -0,0 +1,10 @@ +//! The underlying implementations of the `iced_futures` contract! +pub mod null; + +#[cfg(not(target_arch = "wasm32"))] +pub mod native; + +#[cfg(target_arch = "wasm32")] +pub mod wasm; + +pub mod default; diff --git a/futures/src/backend/default.rs b/futures/src/backend/default.rs new file mode 100644 index 00000000..842b5927 --- /dev/null +++ b/futures/src/backend/default.rs @@ -0,0 +1,45 @@ +//! A default, cross-platform backend. +//! +//! - On native platforms, it will use: +//! - `backend::native::tokio` when the `tokio` feature is enabled. +//! - `backend::native::async-std` when the `async-std` feature is +//! enabled. +//! - `backend::native::smol` when the `smol` feature is enabled. +//! - `backend::native::thread_pool` otherwise. +//! +//! - On Wasm, it will use `backend::wasm::wasm_bindgen`. +#[cfg(not(target_arch = "wasm32"))] +mod platform { + #[cfg(feature = "tokio")] + pub use crate::backend::native::tokio::*; + + #[cfg(all(feature = "async-std", not(feature = "tokio"),))] + pub use crate::backend::native::async_std::*; + + #[cfg(all( + feature = "smol", + not(any(feature = "tokio", feature = "async-std")), + ))] + pub use crate::backend::native::smol::*; + + #[cfg(all( + feature = "thread-pool", + not(any(feature = "tokio", feature = "async-std", feature = "smol")) + ))] + pub use crate::backend::native::thread_pool::*; + + #[cfg(not(any( + feature = "tokio", + feature = "async-std", + feature = "smol", + feature = "thread-pool" + )))] + pub use crate::backend::null::*; +} + +#[cfg(target_arch = "wasm32")] +mod platform { + pub use crate::backend::wasm::wasm_bindgen::*; +} + +pub use platform::*; diff --git a/futures/src/backend/native.rs b/futures/src/backend/native.rs new file mode 100644 index 00000000..4199ad16 --- /dev/null +++ b/futures/src/backend/native.rs @@ -0,0 +1,16 @@ +//! Backends that are only available in native platforms: Windows, macOS, or Linux. +#[cfg_attr(docsrs, doc(cfg(feature = "tokio",)))] +#[cfg(feature = "tokio")] +pub mod tokio; + +#[cfg_attr(docsrs, doc(cfg(feature = "async-std",)))] +#[cfg(feature = "async-std")] +pub mod async_std; + +#[cfg_attr(docsrs, doc(cfg(feature = "smol",)))] +#[cfg(feature = "smol")] +pub mod smol; + +#[cfg_attr(docsrs, doc(cfg(feature = "thread-pool",)))] +#[cfg(feature = "thread-pool")] +pub mod thread_pool; diff --git a/futures/src/backend/native/async_std.rs b/futures/src/backend/native/async_std.rs new file mode 100644 index 00000000..e8641626 --- /dev/null +++ b/futures/src/backend/native/async_std.rs @@ -0,0 +1,59 @@ +//! An `async-std` backend. +use futures::Future; + +/// An `async-std` executor. +#[derive(Debug)] +pub struct Executor; + +impl crate::Executor for Executor { + fn new() -> Result<Self, futures::io::Error> { + Ok(Self) + } + + fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) { + let _ = async_std::task::spawn(future); + } +} + +pub mod time { + //! Listen and react to time. + use crate::subscription::{self, Subscription}; + + /// Returns a [`Subscription`] that produces messages at a set interval. + /// + /// The first message is produced after a `duration`, and then continues to + /// produce more messages every `duration` after that. + pub fn every<H: std::hash::Hasher, E>( + duration: std::time::Duration, + ) -> Subscription<H, E, std::time::Instant> { + Subscription::from_recipe(Every(duration)) + } + + #[derive(Debug)] + struct Every(std::time::Duration); + + impl<H, E> subscription::Recipe<H, E> for Every + where + H: std::hash::Hasher, + { + type Output = std::time::Instant; + + fn hash(&self, state: &mut H) { + use std::hash::Hash; + + std::any::TypeId::of::<Self>().hash(state); + self.0.hash(state); + } + + fn stream( + self: Box<Self>, + _input: futures::stream::BoxStream<'static, E>, + ) -> futures::stream::BoxStream<'static, Self::Output> { + use futures::stream::StreamExt; + + async_std::stream::interval(self.0) + .map(|_| std::time::Instant::now()) + .boxed() + } + } +} diff --git a/futures/src/backend/native/smol.rs b/futures/src/backend/native/smol.rs new file mode 100644 index 00000000..d5201cde --- /dev/null +++ b/futures/src/backend/native/smol.rs @@ -0,0 +1,59 @@ +//! A `smol` backend. + +use futures::Future; + +/// A `smol` executor. +#[cfg_attr(docsrs, doc(cfg(feature = "smol")))] +#[derive(Debug)] +pub struct Executor; + +impl crate::Executor for Executor { + fn new() -> Result<Self, futures::io::Error> { + Ok(Self) + } + + fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) { + smol::spawn(future).detach(); + } +} + +pub mod time { + //! Listen and react to time. + use crate::subscription::{self, Subscription}; + + /// Returns a [`Subscription`] that produces messages at a set interval. + /// + /// The first message is produced after a `duration`, and then continues to + /// produce more messages every `duration` after that. + pub fn every<H: std::hash::Hasher, E>( + duration: std::time::Duration, + ) -> Subscription<H, E, std::time::Instant> { + Subscription::from_recipe(Every(duration)) + } + + #[derive(Debug)] + struct Every(std::time::Duration); + + impl<H, E> subscription::Recipe<H, E> for Every + where + H: std::hash::Hasher, + { + type Output = std::time::Instant; + + fn hash(&self, state: &mut H) { + use std::hash::Hash; + + std::any::TypeId::of::<Self>().hash(state); + self.0.hash(state); + } + + fn stream( + self: Box<Self>, + _input: futures::stream::BoxStream<'static, E>, + ) -> futures::stream::BoxStream<'static, Self::Output> { + use futures::stream::StreamExt; + + smol::Timer::interval(self.0).boxed() + } + } +} diff --git a/futures/src/executor/thread_pool.rs b/futures/src/backend/native/thread_pool.rs index a6c6168e..da5d4b9b 100644 --- a/futures/src/executor/thread_pool.rs +++ b/futures/src/backend/native/thread_pool.rs @@ -1,12 +1,11 @@ -use crate::Executor; - +//! A `ThreadPool` backend. use futures::Future; -/// A thread pool runtime for futures. +/// A thread pool executor for futures. #[cfg_attr(docsrs, doc(cfg(feature = "thread-pool")))] -pub type ThreadPool = futures::executor::ThreadPool; +pub type Executor = futures::executor::ThreadPool; -impl Executor for futures::executor::ThreadPool { +impl crate::Executor for Executor { fn new() -> Result<Self, futures::io::Error> { futures::executor::ThreadPool::new() } @@ -15,3 +14,7 @@ impl Executor for futures::executor::ThreadPool { self.spawn_ok(future); } } + +pub mod time { + //! Listen and react to time. +} diff --git a/futures/src/backend/native/tokio.rs b/futures/src/backend/native/tokio.rs new file mode 100644 index 00000000..f86b0ea3 --- /dev/null +++ b/futures/src/backend/native/tokio.rs @@ -0,0 +1,72 @@ +//! A `tokio` backend. +use futures::Future; + +/// A `tokio` executor. +pub type Executor = tokio::runtime::Runtime; + +impl crate::Executor for Executor { + fn new() -> Result<Self, futures::io::Error> { + tokio::runtime::Runtime::new() + } + + fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) { + let _ = tokio::runtime::Runtime::spawn(self, future); + } + + fn enter<R>(&self, f: impl FnOnce() -> R) -> R { + let _guard = tokio::runtime::Runtime::enter(self); + f() + } +} + +pub mod time { + //! Listen and react to time. + use crate::subscription::{self, Subscription}; + + /// Returns a [`Subscription`] that produces messages at a set interval. + /// + /// The first message is produced after a `duration`, and then continues to + /// produce more messages every `duration` after that. + pub fn every<H: std::hash::Hasher, E>( + duration: std::time::Duration, + ) -> Subscription<H, E, std::time::Instant> { + Subscription::from_recipe(Every(duration)) + } + + #[derive(Debug)] + struct Every(std::time::Duration); + + impl<H, E> subscription::Recipe<H, E> for Every + where + H: std::hash::Hasher, + { + type Output = std::time::Instant; + + fn hash(&self, state: &mut H) { + use std::hash::Hash; + + std::any::TypeId::of::<Self>().hash(state); + self.0.hash(state); + } + + fn stream( + self: Box<Self>, + _input: futures::stream::BoxStream<'static, E>, + ) -> futures::stream::BoxStream<'static, Self::Output> { + use futures::stream::StreamExt; + + let start = tokio::time::Instant::now() + self.0; + + let stream = { + futures::stream::unfold( + tokio::time::interval_at(start, self.0), + |mut interval| async move { + Some((interval.tick().await, interval)) + }, + ) + }; + + stream.map(tokio::time::Instant::into_std).boxed() + } + } +} diff --git a/futures/src/executor/null.rs b/futures/src/backend/null.rs index 65e2e2df..609b8b3f 100644 --- a/futures/src/executor/null.rs +++ b/futures/src/backend/null.rs @@ -1,12 +1,11 @@ -use crate::Executor; - +//! A backend that does nothing! use futures::Future; /// An executor that drops all the futures, instead of spawning them. #[derive(Debug)] -pub struct Null; +pub struct Executor; -impl Executor for Null { +impl crate::Executor for Executor { fn new() -> Result<Self, futures::io::Error> { Ok(Self) } @@ -17,3 +16,7 @@ impl Executor for Null { #[cfg(target_arch = "wasm32")] fn spawn(&self, _future: impl Future<Output = ()> + 'static) {} } + +pub mod time { + //! Listen and react to time. +} diff --git a/futures/src/backend/wasm.rs b/futures/src/backend/wasm.rs new file mode 100644 index 00000000..a49d9e55 --- /dev/null +++ b/futures/src/backend/wasm.rs @@ -0,0 +1,2 @@ +//! Backends that are only available on Wasm targets. +pub mod wasm_bindgen; diff --git a/futures/src/backend/wasm/wasm_bindgen.rs b/futures/src/backend/wasm/wasm_bindgen.rs new file mode 100644 index 00000000..b726501a --- /dev/null +++ b/futures/src/backend/wasm/wasm_bindgen.rs @@ -0,0 +1,59 @@ +//! A `wasm-bindgein-futures` backend. + +/// A `wasm-bindgen-futures` executor. +#[derive(Debug)] +pub struct Executor; + +impl crate::Executor for Executor { + fn new() -> Result<Self, futures::io::Error> { + Ok(Self) + } + + fn spawn(&self, future: impl futures::Future<Output = ()> + 'static) { + wasm_bindgen_futures::spawn_local(future); + } +} + +pub mod time { + //! Listen and react to time. + use crate::subscription::{self, Subscription}; + use crate::BoxStream; + + /// Returns a [`Subscription`] that produces messages at a set interval. + /// + /// The first message is produced after a `duration`, and then continues to + /// produce more messages every `duration` after that. + pub fn every<H: std::hash::Hasher, E>( + duration: std::time::Duration, + ) -> Subscription<H, E, wasm_timer::Instant> { + Subscription::from_recipe(Every(duration)) + } + + #[derive(Debug)] + struct Every(std::time::Duration); + + impl<H, E> subscription::Recipe<H, E> for Every + where + H: std::hash::Hasher, + { + type Output = wasm_timer::Instant; + + fn hash(&self, state: &mut H) { + use std::hash::Hash; + + std::any::TypeId::of::<Self>().hash(state); + self.0.hash(state); + } + + fn stream( + self: Box<Self>, + _input: BoxStream<E>, + ) -> BoxStream<Self::Output> { + use futures::stream::StreamExt; + + wasm_timer::Interval::new(self.0) + .map(|_| wasm_timer::Instant::now()) + .boxed_local() + } + } +} diff --git a/futures/src/executor.rs b/futures/src/executor.rs index 23682f32..5ac76081 100644 --- a/futures/src/executor.rs +++ b/futures/src/executor.rs @@ -1,38 +1,5 @@ //! Choose your preferred executor to power a runtime. -mod null; - -#[cfg(all(not(target_arch = "wasm32"), feature = "thread-pool"))] -mod thread_pool; - -#[cfg(all(not(target_arch = "wasm32"), feature = "tokio"))] -mod tokio; - -#[cfg(all(not(target_arch = "wasm32"), feature = "async-std"))] -mod async_std; - -#[cfg(all(not(target_arch = "wasm32"), feature = "smol"))] -mod smol; - -#[cfg(target_arch = "wasm32")] -mod wasm_bindgen; - -pub use null::Null; - -#[cfg(all(not(target_arch = "wasm32"), feature = "thread-pool"))] -pub use thread_pool::ThreadPool; - -#[cfg(all(not(target_arch = "wasm32"), feature = "tokio"))] -pub use self::tokio::Tokio; - -#[cfg(all(not(target_arch = "wasm32"), feature = "async-std"))] -pub use self::async_std::AsyncStd; - -#[cfg(all(not(target_arch = "wasm32"), feature = "smol"))] -pub use self::smol::Smol; - -#[cfg(target_arch = "wasm32")] -pub use wasm_bindgen::WasmBindgen; - +use crate::MaybeSend; use futures::Future; /// A type that can run futures. @@ -43,12 +10,7 @@ pub trait Executor: Sized { Self: Sized; /// Spawns a future in the [`Executor`]. - #[cfg(not(target_arch = "wasm32"))] - fn spawn(&self, future: impl Future<Output = ()> + Send + 'static); - - /// Spawns a local future in the [`Executor`]. - #[cfg(target_arch = "wasm32")] - fn spawn(&self, future: impl Future<Output = ()> + 'static); + fn spawn(&self, future: impl Future<Output = ()> + MaybeSend + 'static); /// Runs the given closure inside the [`Executor`]. /// diff --git a/futures/src/executor/async_std.rs b/futures/src/executor/async_std.rs deleted file mode 100644 index 471be369..00000000 --- a/futures/src/executor/async_std.rs +++ /dev/null @@ -1,18 +0,0 @@ -use crate::Executor; - -use futures::Future; - -/// An `async-std` runtime. -#[cfg_attr(docsrs, doc(cfg(feature = "async-std")))] -#[derive(Debug)] -pub struct AsyncStd; - -impl Executor for AsyncStd { - fn new() -> Result<Self, futures::io::Error> { - Ok(Self) - } - - fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) { - let _ = async_std::task::spawn(future); - } -} diff --git a/futures/src/executor/smol.rs b/futures/src/executor/smol.rs deleted file mode 100644 index deafd43a..00000000 --- a/futures/src/executor/smol.rs +++ /dev/null @@ -1,18 +0,0 @@ -use crate::Executor; - -use futures::Future; - -/// A `smol` runtime. -#[cfg_attr(docsrs, doc(cfg(feature = "smol")))] -#[derive(Debug)] -pub struct Smol; - -impl Executor for Smol { - fn new() -> Result<Self, futures::io::Error> { - Ok(Self) - } - - fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) { - smol::spawn(future).detach(); - } -} diff --git a/futures/src/executor/tokio.rs b/futures/src/executor/tokio.rs deleted file mode 100644 index c6a21cec..00000000 --- a/futures/src/executor/tokio.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::Executor; - -use futures::Future; - -/// A `tokio` runtime. -#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] -pub type Tokio = tokio::runtime::Runtime; - -impl Executor for Tokio { - fn new() -> Result<Self, futures::io::Error> { - tokio::runtime::Runtime::new() - } - - fn spawn(&self, future: impl Future<Output = ()> + Send + 'static) { - let _ = tokio::runtime::Runtime::spawn(self, future); - } - - fn enter<R>(&self, f: impl FnOnce() -> R) -> R { - let _guard = tokio::runtime::Runtime::enter(self); - f() - } -} diff --git a/futures/src/executor/wasm_bindgen.rs b/futures/src/executor/wasm_bindgen.rs deleted file mode 100644 index 94d694c8..00000000 --- a/futures/src/executor/wasm_bindgen.rs +++ /dev/null @@ -1,15 +0,0 @@ -use crate::Executor; - -/// A `wasm-bindgen-futures` runtime. -#[derive(Debug)] -pub struct WasmBindgen; - -impl Executor for WasmBindgen { - fn new() -> Result<Self, futures::io::Error> { - Ok(Self) - } - - fn spawn(&self, future: impl futures::Future<Output = ()> + 'static) { - wasm_bindgen_futures::spawn_local(future); - } -} diff --git a/futures/src/lib.rs b/futures/src/lib.rs index dbcb8aca..b0b2f6ce 100644 --- a/futures/src/lib.rs +++ b/futures/src/lib.rs @@ -14,27 +14,16 @@ pub use futures; mod command; +mod maybe_send; mod runtime; +pub mod backend; pub mod executor; pub mod subscription; -#[cfg(all( - any(feature = "tokio", feature = "async-std", feature = "smol"), - not(target_arch = "wasm32") -))] -#[cfg_attr( - docsrs, - doc(cfg(any( - feature = "tokio", - feature = "async-std", - feature = "smol" - ))) -)] -pub mod time; - pub use command::Command; pub use executor::Executor; +pub use maybe_send::MaybeSend; pub use platform::*; pub use runtime::Runtime; pub use subscription::Subscription; diff --git a/futures/src/maybe_send.rs b/futures/src/maybe_send.rs new file mode 100644 index 00000000..a6670f0e --- /dev/null +++ b/futures/src/maybe_send.rs @@ -0,0 +1,21 @@ +#[cfg(not(target_arch = "wasm32"))] +mod platform { + /// An extension trait that enforces `Send` only on native platforms. + /// + /// Useful to write cross-platform async code! + pub trait MaybeSend: Send {} + + impl<T> MaybeSend for T where T: Send {} +} + +#[cfg(target_arch = "wasm32")] +mod platform { + /// An extension trait that enforces `Send` only on native platforms. + /// + /// Useful to write cross-platform async code! + pub trait MaybeSend {} + + impl<T> MaybeSend for T {} +} + +pub use platform::MaybeSend; diff --git a/futures/src/runtime.rs b/futures/src/runtime.rs index 7779e235..2034ed6c 100644 --- a/futures/src/runtime.rs +++ b/futures/src/runtime.rs @@ -1,6 +1,6 @@ //! Run commands and keep track of subscriptions. -use crate::BoxFuture; -use crate::{subscription, Executor, Subscription}; +use crate::subscription; +use crate::{BoxFuture, Executor, MaybeSend, Subscription}; use futures::{channel::mpsc, Sink}; use std::marker::PhantomData; @@ -23,9 +23,12 @@ where Hasher: std::hash::Hasher + Default, Event: Send + Clone + 'static, Executor: self::Executor, - Sender: - Sink<Message, Error = mpsc::SendError> + Unpin + Send + Clone + 'static, - Message: Send + 'static, + Sender: Sink<Message, Error = mpsc::SendError> + + Unpin + + MaybeSend + + Clone + + 'static, + Message: MaybeSend + 'static, { /// Creates a new empty [`Runtime`]. /// diff --git a/futures/src/subscription/tracker.rs b/futures/src/subscription/tracker.rs index 3a8d4a87..421fb917 100644 --- a/futures/src/subscription/tracker.rs +++ b/futures/src/subscription/tracker.rs @@ -1,4 +1,4 @@ -use crate::{BoxFuture, Subscription}; +use crate::{BoxFuture, MaybeSend, Subscription}; use futures::{channel::mpsc, sink::Sink}; use std::{collections::HashMap, marker::PhantomData}; @@ -57,11 +57,11 @@ where receiver: Receiver, ) -> Vec<BoxFuture<()>> where - Message: 'static + Send, + Message: 'static + MaybeSend, Receiver: 'static + Sink<Message, Error = mpsc::SendError> + Unpin - + Send + + MaybeSend + Clone, { use futures::{future::FutureExt, stream::StreamExt}; diff --git a/futures/src/time.rs b/futures/src/time.rs deleted file mode 100644 index 0ece6f04..00000000 --- a/futures/src/time.rs +++ /dev/null @@ -1,105 +0,0 @@ -//! Listen and react to time. -use crate::subscription::{self, Subscription}; - -/// Returns a [`Subscription`] that produces messages at a set interval. -/// -/// The first message is produced after a `duration`, and then continues to -/// produce more messages every `duration` after that. -pub fn every<H: std::hash::Hasher, E>( - duration: std::time::Duration, -) -> Subscription<H, E, std::time::Instant> { - Subscription::from_recipe(Every(duration)) -} - -struct Every(std::time::Duration); - -#[cfg(all( - not(any(feature = "tokio", feature = "async-std")), - feature = "smol" -))] -impl<H, E> subscription::Recipe<H, E> for Every -where - H: std::hash::Hasher, -{ - type Output = std::time::Instant; - - fn hash(&self, state: &mut H) { - use std::hash::Hash; - - std::any::TypeId::of::<Self>().hash(state); - self.0.hash(state); - } - - fn stream( - self: Box<Self>, - _input: futures::stream::BoxStream<'static, E>, - ) -> futures::stream::BoxStream<'static, Self::Output> { - use futures::stream::StreamExt; - - smol::Timer::interval(self.0).boxed() - } -} - -#[cfg(feature = "async-std")] -impl<H, E> subscription::Recipe<H, E> for Every -where - H: std::hash::Hasher, -{ - type Output = std::time::Instant; - - fn hash(&self, state: &mut H) { - use std::hash::Hash; - - std::any::TypeId::of::<Self>().hash(state); - self.0.hash(state); - } - - fn stream( - self: Box<Self>, - _input: futures::stream::BoxStream<'static, E>, - ) -> futures::stream::BoxStream<'static, Self::Output> { - use futures::stream::StreamExt; - - async_std::stream::interval(self.0) - .map(|_| std::time::Instant::now()) - .boxed() - } -} - -#[cfg(all( - feature = "tokio", - not(any(feature = "async-std", feature = "smol")) -))] -impl<H, E> subscription::Recipe<H, E> for Every -where - H: std::hash::Hasher, -{ - type Output = std::time::Instant; - - fn hash(&self, state: &mut H) { - use std::hash::Hash; - - std::any::TypeId::of::<Self>().hash(state); - self.0.hash(state); - } - - fn stream( - self: Box<Self>, - _input: futures::stream::BoxStream<'static, E>, - ) -> futures::stream::BoxStream<'static, Self::Output> { - use futures::stream::StreamExt; - - let start = tokio::time::Instant::now() + self.0; - - let stream = { - futures::stream::unfold( - tokio::time::interval_at(start, self.0), - |mut interval| async move { - Some((interval.tick().await, interval)) - }, - ) - }; - - stream.map(tokio::time::Instant::into_std).boxed() - } -} diff --git a/glow/Cargo.toml b/glow/Cargo.toml index e40b8ba8..e0907a66 100644 --- a/glow/Cargo.toml +++ b/glow/Cargo.toml @@ -2,7 +2,7 @@ name = "iced_glow" version = "0.2.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" description = "A glow renderer for iced" license = "MIT AND OFL-1.1" repository = "https://github.com/hecrj/iced" @@ -16,8 +16,8 @@ image = [] svg = [] [dependencies] -glow = "0.6" -glow_glyph = "0.4" +glow = "0.11.1" +glow_glyph = "0.5.0" glyph_brush = "0.7" euclid = "0.22" bytemuck = "1.4" diff --git a/glow/README.md b/glow/README.md new file mode 100644 index 00000000..5e37b7a2 --- /dev/null +++ b/glow/README.md @@ -0,0 +1,51 @@ +# `iced_glow` +[][documentation] +[](https://crates.io/crates/iced_glow) +[](https://github.com/hecrj/iced/blob/master/LICENSE) +[](https://discord.gg/3xZJ65GAhd) + +`iced_glow` is a [`glow`] renderer for [`iced_native`]. This renderer supports OpenGL 3.0+ and OpenGL ES 2.0. + +This is renderer is mostly used as a fallback for hardware that doesn't support [`wgpu`] (Vulkan, Metal or DX12). + +Currently, `iced_glow` supports the following primitives: +- Text, which is rendered using [`glow_glyph`]. No shaping at all. +- Quads or rectangles, with rounded borders and a solid background color. +- Clip areas, useful to implement scrollables or hide overflowing content. +- Meshes of triangles, useful to draw geometry freely. + +<p align="center"> + <img alt="The native target" src="../docs/graphs/native.png" width="80%"> +</p> + +[documentation]: https://docs.rs/iced_glow +[`iced_native`]: ../native +[`glow`]: https://github.com/grovesNL/glow +[`wgpu`]: https://github.com/gfx-rs/wgpu +[`glow_glyph`]: https://github.com/hecrj/glow_glyph + +## Installation +Add `iced_glow` as a dependency in your `Cargo.toml`: + +```toml +iced_glow = "0.2" +``` + +__Iced moves fast and the `master` branch can contain breaking changes!__ If +you want to learn about a specific release, check out [the release list]. + +[the release list]: https://github.com/hecrj/iced/releases + +## Current limitations + +The current implementation is quite naive, it uses: + +- A different pipeline/shader for each primitive +- A very simplistic layer model: every `Clip` primitive will generate new layers +- _Many_ render passes instead of preparing everything upfront +- A glyph cache that is trimmed incorrectly when there are multiple layers (a [`glyph_brush`] limitation) + +Some of these issues are already being worked on! If you want to help, [get in touch!] + +[get in touch!]: ../CONTRIBUTING.md +[`glyph_brush`]: https://github.com/alexheretic/glyph-brush diff --git a/glow/src/backend.rs b/glow/src/backend.rs index 5ab7f922..89dc1aaa 100644 --- a/glow/src/backend.rs +++ b/glow/src/backend.rs @@ -1,3 +1,4 @@ +use crate::program; use crate::quad; use crate::text; use crate::triangle; @@ -30,8 +31,10 @@ impl Backend { settings.text_multithreading, ); - let quad_pipeline = quad::Pipeline::new(gl); - let triangle_pipeline = triangle::Pipeline::new(gl); + let shader_version = program::Version::new(gl); + + let quad_pipeline = quad::Pipeline::new(gl, &shader_version); + let triangle_pipeline = triangle::Pipeline::new(gl, &shader_version); Self { quad_pipeline, diff --git a/glow/src/lib.rs b/glow/src/lib.rs index 362933d4..4e5a75d7 100644 --- a/glow/src/lib.rs +++ b/glow/src/lib.rs @@ -13,6 +13,8 @@ #![forbid(rust_2018_idioms)] #![cfg_attr(docsrs, feature(doc_cfg))] +pub use glow; + mod backend; mod program; mod quad; diff --git a/glow/src/program.rs b/glow/src/program.rs index 601f9ce6..9a02d578 100644 --- a/glow/src/program.rs +++ b/glow/src/program.rs @@ -1,28 +1,122 @@ use glow::HasContext; -pub unsafe fn create( - gl: &glow::Context, - shader_sources: &[(u32, &str)], -) -> <glow::Context as HasContext>::Program { - let program = gl.create_program().expect("Cannot create program"); +/// The [`Version`] of a `Program`. +pub struct Version { + vertex: String, + fragment: String, +} + +impl Version { + pub fn new(gl: &glow::Context) -> Version { + let version = gl.version(); + + let (vertex, fragment) = match ( + version.major, + version.minor, + version.is_embedded, + ) { + // OpenGL 3.0+ + (3, 0 | 1 | 2, false) => ( + format!("#version 1{}0", version.minor + 3), + format!( + "#version 1{}0\n#define HIGHER_THAN_300 1", + version.minor + 3 + ), + ), + // OpenGL 3.3+ + (3 | 4, _, false) => ( + format!("#version {}{}0", version.major, version.minor), + format!( + "#version {}{}0\n#define HIGHER_THAN_300 1", + version.major, version.minor + ), + ), + // OpenGL ES 3.0+ + (3, _, true) => ( + format!("#version 3{}0 es", version.minor), + format!( + "#version 3{}0 es\n#define HIGHER_THAN_300 1", + version.minor + ), + ), + // OpenGL ES 2.0+ + (2, _, true) => ( + String::from( + "#version 100\n#define in attribute\n#define out varying", + ), + String::from("#version 100\n#define in varying"), + ), + // OpenGL 2.1 + (2, _, false) => ( + String::from( + "#version 120\n#define in attribute\n#define out varying", + ), + String::from("#version 120\n#define in varying"), + ), + // OpenGL 1.1+ + _ => panic!("Incompatible context version: {:?}", version), + }; - let mut shaders = Vec::with_capacity(shader_sources.len()); + log::info!("Shader directive: {}", vertex.lines().next().unwrap()); - for (shader_type, shader_source) in shader_sources.iter() { - let shader = gl - .create_shader(*shader_type) - .expect("Cannot create shader"); + Version { vertex, fragment } + } +} + +pub struct Shader(<glow::Context as HasContext>::Shader); + +impl Shader { + fn compile(gl: &glow::Context, stage: u32, content: &str) -> Shader { + unsafe { + let shader = gl.create_shader(stage).expect("Cannot create shader"); - gl.shader_source(shader, shader_source); - gl.compile_shader(shader); + gl.shader_source(shader, &content); + gl.compile_shader(shader); - if !gl.get_shader_compile_status(shader) { - panic!("{}", gl.get_shader_info_log(shader)); + if !gl.get_shader_compile_status(shader) { + panic!("{}", gl.get_shader_info_log(shader)); + } + + Shader(shader) } + } + + /// Creates a vertex [`Shader`]. + pub fn vertex( + gl: &glow::Context, + version: &Version, + content: &'static str, + ) -> Self { + let content = format!("{}\n{}", version.vertex, content); - gl.attach_shader(program, shader); + Shader::compile(gl, glow::VERTEX_SHADER, &content) + } + + /// Creates a fragment [`Shader`]. + pub fn fragment( + gl: &glow::Context, + version: &Version, + content: &'static str, + ) -> Self { + let content = format!("{}\n{}", version.fragment, content); + + Shader::compile(gl, glow::FRAGMENT_SHADER, &content) + } +} + +pub unsafe fn create( + gl: &glow::Context, + shaders: &[Shader], + attributes: &[(u32, &str)], +) -> <glow::Context as HasContext>::Program { + let program = gl.create_program().expect("Cannot create program"); + + for shader in shaders { + gl.attach_shader(program, shader.0); + } - shaders.push(shader); + for (i, name) in attributes { + gl.bind_attrib_location(program, *i, name); } gl.link_program(program); @@ -31,8 +125,8 @@ pub unsafe fn create( } for shader in shaders { - gl.detach_shader(program, shader); - gl.delete_shader(shader); + gl.detach_shader(program, shader.0); + gl.delete_shader(shader.0); } program diff --git a/glow/src/quad.rs b/glow/src/quad.rs index a8fbb9e5..d9f1c6ae 100644 --- a/glow/src/quad.rs +++ b/glow/src/quad.rs @@ -1,77 +1,35 @@ +mod compatibility; +mod core; + use crate::program; use crate::Transformation; use glow::HasContext; use iced_graphics::layer; use iced_native::Rectangle; -const MAX_INSTANCES: usize = 100_000; - #[derive(Debug)] -pub struct Pipeline { - program: <glow::Context as HasContext>::Program, - vertex_array: <glow::Context as HasContext>::VertexArray, - instances: <glow::Context as HasContext>::Buffer, - transform_location: <glow::Context as HasContext>::UniformLocation, - scale_location: <glow::Context as HasContext>::UniformLocation, - screen_height_location: <glow::Context as HasContext>::UniformLocation, - current_transform: Transformation, - current_scale: f32, - current_target_height: u32, +pub enum Pipeline { + Core(core::Pipeline), + Compatibility(compatibility::Pipeline), } impl Pipeline { - pub fn new(gl: &glow::Context) -> Pipeline { - let program = unsafe { - program::create( + pub fn new( + gl: &glow::Context, + shader_version: &program::Version, + ) -> Pipeline { + let gl_version = gl.version(); + + // OpenGL 3.0+ and OpenGL ES 3.0+ have instancing (which is what separates `core` from `compatibility`) + if gl_version.major >= 3 { + log::info!("Mode: core"); + Pipeline::Core(core::Pipeline::new(gl, shader_version)) + } else { + log::info!("Mode: compatibility"); + Pipeline::Compatibility(compatibility::Pipeline::new( gl, - &[ - (glow::VERTEX_SHADER, include_str!("shader/quad.vert")), - (glow::FRAGMENT_SHADER, include_str!("shader/quad.frag")), - ], - ) - }; - - let transform_location = - unsafe { gl.get_uniform_location(program, "u_Transform") } - .expect("Get transform location"); - - let scale_location = - unsafe { gl.get_uniform_location(program, "u_Scale") } - .expect("Get scale location"); - - let screen_height_location = - unsafe { gl.get_uniform_location(program, "u_ScreenHeight") } - .expect("Get target height location"); - - unsafe { - gl.use_program(Some(program)); - - let matrix: [f32; 16] = Transformation::identity().into(); - gl.uniform_matrix_4_f32_slice( - Some(&transform_location), - false, - &matrix, - ); - - gl.uniform_1_f32(Some(&scale_location), 1.0); - gl.uniform_1_f32(Some(&screen_height_location), 0.0); - - gl.use_program(None); - } - - let (vertex_array, instances) = - unsafe { create_instance_buffer(gl, MAX_INSTANCES) }; - - Pipeline { - program, - vertex_array, - instances, - transform_location, - scale_location, - screen_height_location, - current_transform: Transformation::identity(), - current_scale: 1.0, - current_target_height: 0, + shader_version, + )) } } @@ -84,152 +42,27 @@ impl Pipeline { scale: f32, bounds: Rectangle<u32>, ) { - unsafe { - gl.enable(glow::SCISSOR_TEST); - gl.scissor( - bounds.x as i32, - (target_height - (bounds.y + bounds.height)) as i32, - bounds.width as i32, - bounds.height as i32, - ); - - gl.use_program(Some(self.program)); - gl.bind_vertex_array(Some(self.vertex_array)); - gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.instances)); - } - - if transformation != self.current_transform { - unsafe { - let matrix: [f32; 16] = transformation.into(); - gl.uniform_matrix_4_f32_slice( - Some(&self.transform_location), - false, - &matrix, + match self { + Pipeline::Core(pipeline) => { + pipeline.draw( + gl, + target_height, + instances, + transformation, + scale, + bounds, ); - - self.current_transform = transformation; - } - } - - if scale != self.current_scale { - unsafe { - gl.uniform_1_f32(Some(&self.scale_location), scale); } - - self.current_scale = scale; - } - - if target_height != self.current_target_height { - unsafe { - gl.uniform_1_f32( - Some(&self.screen_height_location), - target_height as f32, + Pipeline::Compatibility(pipeline) => { + pipeline.draw( + gl, + target_height, + instances, + transformation, + scale, + bounds, ); } - - self.current_target_height = target_height; - } - - let mut i = 0; - let total = instances.len(); - - while i < total { - let end = (i + MAX_INSTANCES).min(total); - let amount = end - i; - - unsafe { - gl.buffer_sub_data_u8_slice( - glow::ARRAY_BUFFER, - 0, - bytemuck::cast_slice(&instances[i..end]), - ); - - gl.draw_arrays_instanced( - glow::TRIANGLE_STRIP, - 0, - 4, - amount as i32, - ); - } - - i += MAX_INSTANCES; - } - - unsafe { - gl.bind_vertex_array(None); - gl.use_program(None); - gl.disable(glow::SCISSOR_TEST); } } } - -unsafe fn create_instance_buffer( - gl: &glow::Context, - size: usize, -) -> ( - <glow::Context as HasContext>::VertexArray, - <glow::Context as HasContext>::Buffer, -) { - let vertex_array = gl.create_vertex_array().expect("Create vertex array"); - let buffer = gl.create_buffer().expect("Create instance buffer"); - - gl.bind_vertex_array(Some(vertex_array)); - gl.bind_buffer(glow::ARRAY_BUFFER, Some(buffer)); - gl.buffer_data_size( - glow::ARRAY_BUFFER, - (size * std::mem::size_of::<layer::Quad>()) as i32, - glow::DYNAMIC_DRAW, - ); - - let stride = std::mem::size_of::<layer::Quad>() as i32; - - gl.enable_vertex_attrib_array(0); - gl.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, stride, 0); - gl.vertex_attrib_divisor(0, 1); - - gl.enable_vertex_attrib_array(1); - gl.vertex_attrib_pointer_f32(1, 2, glow::FLOAT, false, stride, 4 * 2); - gl.vertex_attrib_divisor(1, 1); - - gl.enable_vertex_attrib_array(2); - gl.vertex_attrib_pointer_f32(2, 4, glow::FLOAT, false, stride, 4 * (2 + 2)); - gl.vertex_attrib_divisor(2, 1); - - gl.enable_vertex_attrib_array(3); - gl.vertex_attrib_pointer_f32( - 3, - 4, - glow::FLOAT, - false, - stride, - 4 * (2 + 2 + 4), - ); - gl.vertex_attrib_divisor(3, 1); - - gl.enable_vertex_attrib_array(4); - gl.vertex_attrib_pointer_f32( - 4, - 1, - glow::FLOAT, - false, - stride, - 4 * (2 + 2 + 4 + 4), - ); - gl.vertex_attrib_divisor(4, 1); - - gl.enable_vertex_attrib_array(5); - gl.vertex_attrib_pointer_f32( - 5, - 1, - glow::FLOAT, - false, - stride, - 4 * (2 + 2 + 4 + 4 + 1), - ); - gl.vertex_attrib_divisor(5, 1); - - gl.bind_vertex_array(None); - gl.bind_buffer(glow::ARRAY_BUFFER, None); - - (vertex_array, buffer) -} diff --git a/glow/src/quad/compatibility.rs b/glow/src/quad/compatibility.rs new file mode 100644 index 00000000..76f98ab7 --- /dev/null +++ b/glow/src/quad/compatibility.rs @@ -0,0 +1,360 @@ +use crate::program::{self, Shader}; +use crate::Transformation; +use glow::HasContext; +use iced_graphics::layer; +use iced_native::Rectangle; + +// Only change `MAX_QUADS`, otherwise you could cause problems +// by splitting a triangle into different render passes. +const MAX_QUADS: usize = 100_000; +const MAX_VERTICES: usize = MAX_QUADS * 4; +const MAX_INDICES: usize = MAX_QUADS * 6; + +#[derive(Debug)] +pub struct Pipeline { + program: <glow::Context as HasContext>::Program, + vertex_array: <glow::Context as HasContext>::VertexArray, + vertex_buffer: <glow::Context as HasContext>::Buffer, + index_buffer: <glow::Context as HasContext>::Buffer, + transform_location: <glow::Context as HasContext>::UniformLocation, + scale_location: <glow::Context as HasContext>::UniformLocation, + screen_height_location: <glow::Context as HasContext>::UniformLocation, + current_transform: Transformation, + current_scale: f32, + current_target_height: u32, +} + +impl Pipeline { + pub fn new( + gl: &glow::Context, + shader_version: &program::Version, + ) -> Pipeline { + let program = unsafe { + let vertex_shader = Shader::vertex( + gl, + shader_version, + include_str!("../shader/compatibility/quad.vert"), + ); + let fragment_shader = Shader::fragment( + gl, + shader_version, + include_str!("../shader/compatibility/quad.frag"), + ); + + program::create( + gl, + &[vertex_shader, fragment_shader], + &[ + (0, "i_Pos"), + (1, "i_Scale"), + (2, "i_Color"), + (3, "i_BorderColor"), + (4, "i_BorderRadius"), + (5, "i_BorderWidth"), + ], + ) + }; + + let transform_location = + unsafe { gl.get_uniform_location(program, "u_Transform") } + .expect("Get transform location"); + + let scale_location = + unsafe { gl.get_uniform_location(program, "u_Scale") } + .expect("Get scale location"); + + let screen_height_location = + unsafe { gl.get_uniform_location(program, "u_ScreenHeight") } + .expect("Get target height location"); + + unsafe { + gl.use_program(Some(program)); + + let matrix: [f32; 16] = Transformation::identity().into(); + gl.uniform_matrix_4_f32_slice( + Some(&transform_location), + false, + &matrix, + ); + + gl.uniform_1_f32(Some(&scale_location), 1.0); + gl.uniform_1_f32(Some(&screen_height_location), 0.0); + + gl.use_program(None); + } + + let (vertex_array, vertex_buffer, index_buffer) = + unsafe { create_buffers(gl, MAX_VERTICES) }; + + Pipeline { + program, + vertex_array, + vertex_buffer, + index_buffer, + transform_location, + scale_location, + screen_height_location, + current_transform: Transformation::identity(), + current_scale: 1.0, + current_target_height: 0, + } + } + + pub fn draw( + &mut self, + gl: &glow::Context, + target_height: u32, + instances: &[layer::Quad], + transformation: Transformation, + scale: f32, + bounds: Rectangle<u32>, + ) { + // TODO: Remove this allocation (probably by changing the shader and removing the need of two `position`) + let vertices: Vec<Vertex> = instances + .iter() + .flat_map(|quad| Vertex::from_quad(quad)) + .collect(); + + // TODO: Remove this allocation (or allocate only when needed) + let indices: Vec<i32> = (0..instances.len().min(MAX_QUADS) as i32) + .flat_map(|i| { + [ + 0 + i * 4, + 1 + i * 4, + 2 + i * 4, + 2 + i * 4, + 1 + i * 4, + 3 + i * 4, + ] + }) + .cycle() + .take(instances.len() * 6) + .collect(); + + unsafe { + gl.enable(glow::SCISSOR_TEST); + gl.scissor( + bounds.x as i32, + (target_height - (bounds.y + bounds.height)) as i32, + bounds.width as i32, + bounds.height as i32, + ); + + gl.use_program(Some(self.program)); + gl.bind_vertex_array(Some(self.vertex_array)); + gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer)); + gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.index_buffer)); + } + + if transformation != self.current_transform { + unsafe { + let matrix: [f32; 16] = transformation.into(); + gl.uniform_matrix_4_f32_slice( + Some(&self.transform_location), + false, + &matrix, + ); + + self.current_transform = transformation; + } + } + + if scale != self.current_scale { + unsafe { + gl.uniform_1_f32(Some(&self.scale_location), scale); + } + + self.current_scale = scale; + } + + if target_height != self.current_target_height { + unsafe { + gl.uniform_1_f32( + Some(&self.screen_height_location), + target_height as f32, + ); + } + + self.current_target_height = target_height; + } + + let passes = vertices + .chunks(MAX_VERTICES) + .zip(indices.chunks(MAX_INDICES)); + + for (vertices, indices) in passes { + unsafe { + gl.buffer_sub_data_u8_slice( + glow::ARRAY_BUFFER, + 0, + bytemuck::cast_slice(&vertices), + ); + + gl.buffer_sub_data_u8_slice( + glow::ELEMENT_ARRAY_BUFFER, + 0, + bytemuck::cast_slice(&indices), + ); + + gl.draw_elements( + glow::TRIANGLES, + indices.len() as i32, + glow::UNSIGNED_INT, + 0, + ); + } + } + + unsafe { + gl.bind_vertex_array(None); + gl.use_program(None); + gl.disable(glow::SCISSOR_TEST); + } + } +} + +unsafe fn create_buffers( + gl: &glow::Context, + size: usize, +) -> ( + <glow::Context as HasContext>::VertexArray, + <glow::Context as HasContext>::Buffer, + <glow::Context as HasContext>::Buffer, +) { + let vertex_array = gl.create_vertex_array().expect("Create vertex array"); + let vertex_buffer = gl.create_buffer().expect("Create vertex buffer"); + let index_buffer = gl.create_buffer().expect("Create index buffer"); + + gl.bind_vertex_array(Some(vertex_array)); + + gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(index_buffer)); + gl.buffer_data_size( + glow::ELEMENT_ARRAY_BUFFER, + 12 * size as i32, + glow::DYNAMIC_DRAW, + ); + + gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer)); + gl.buffer_data_size( + glow::ARRAY_BUFFER, + (size * Vertex::SIZE) as i32, + glow::DYNAMIC_DRAW, + ); + + let stride = Vertex::SIZE as i32; + + gl.enable_vertex_attrib_array(0); + gl.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, stride, 0); + + gl.enable_vertex_attrib_array(1); + gl.vertex_attrib_pointer_f32(1, 2, glow::FLOAT, false, stride, 4 * 2); + + gl.enable_vertex_attrib_array(2); + gl.vertex_attrib_pointer_f32(2, 4, glow::FLOAT, false, stride, 4 * (2 + 2)); + + gl.enable_vertex_attrib_array(3); + gl.vertex_attrib_pointer_f32( + 3, + 4, + glow::FLOAT, + false, + stride, + 4 * (2 + 2 + 4), + ); + + gl.enable_vertex_attrib_array(4); + gl.vertex_attrib_pointer_f32( + 4, + 1, + glow::FLOAT, + false, + stride, + 4 * (2 + 2 + 4 + 4), + ); + + gl.enable_vertex_attrib_array(5); + gl.vertex_attrib_pointer_f32( + 5, + 1, + glow::FLOAT, + false, + stride, + 4 * (2 + 2 + 4 + 4 + 1), + ); + + gl.enable_vertex_attrib_array(6); + gl.vertex_attrib_pointer_f32( + 6, + 2, + glow::FLOAT, + false, + stride, + 4 * (2 + 2 + 4 + 4 + 1 + 1), + ); + + gl.bind_vertex_array(None); + gl.bind_buffer(glow::ARRAY_BUFFER, None); + gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None); + + (vertex_array, vertex_buffer, index_buffer) +} + +/// The vertex of a colored rectangle with a border. +/// +/// This type can be directly uploaded to GPU memory. +#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(C)] +pub struct Vertex { + /// The position of the [`Vertex`]. + pub position: [f32; 2], + + /// The size of the [`Vertex`]. + pub size: [f32; 2], + + /// The color of the [`Vertex`], in __linear RGB__. + pub color: [f32; 4], + + /// The border color of the [`Vertex`], in __linear RGB__. + pub border_color: [f32; 4], + + /// The border radius of the [`Vertex`]. + pub border_radius: f32, + + /// The border width of the [`Vertex`]. + pub border_width: f32, + + /// The __quad__ position of the [`Vertex`]. + pub q_position: [f32; 2], +} + +impl Vertex { + const SIZE: usize = std::mem::size_of::<Self>(); + + fn from_quad(quad: &layer::Quad) -> [Vertex; 4] { + let base = Vertex { + position: quad.position, + size: quad.size, + color: quad.color, + border_color: quad.color, + border_radius: quad.border_radius, + border_width: quad.border_width, + q_position: [0.0, 0.0], + }; + + [ + base, + Self { + q_position: [0.0, 1.0], + ..base + }, + Self { + q_position: [1.0, 0.0], + ..base + }, + Self { + q_position: [1.0, 1.0], + ..base + }, + ] + } +} diff --git a/glow/src/quad/core.rs b/glow/src/quad/core.rs new file mode 100644 index 00000000..f37300f6 --- /dev/null +++ b/glow/src/quad/core.rs @@ -0,0 +1,246 @@ +use crate::program::{self, Shader}; +use crate::Transformation; +use glow::HasContext; +use iced_graphics::layer; +use iced_native::Rectangle; + +const MAX_INSTANCES: usize = 100_000; + +#[derive(Debug)] +pub struct Pipeline { + program: <glow::Context as HasContext>::Program, + vertex_array: <glow::Context as HasContext>::VertexArray, + instances: <glow::Context as HasContext>::Buffer, + transform_location: <glow::Context as HasContext>::UniformLocation, + scale_location: <glow::Context as HasContext>::UniformLocation, + screen_height_location: <glow::Context as HasContext>::UniformLocation, + current_transform: Transformation, + current_scale: f32, + current_target_height: u32, +} + +impl Pipeline { + pub fn new( + gl: &glow::Context, + shader_version: &program::Version, + ) -> Pipeline { + let program = unsafe { + let vertex_shader = Shader::vertex( + gl, + shader_version, + include_str!("../shader/core/quad.vert"), + ); + let fragment_shader = Shader::fragment( + gl, + shader_version, + include_str!("../shader/core/quad.frag"), + ); + + program::create( + gl, + &[vertex_shader, fragment_shader], + &[ + (0, "i_Pos"), + (1, "i_Scale"), + (2, "i_Color"), + (3, "i_BorderColor"), + (4, "i_BorderRadius"), + (5, "i_BorderWidth"), + ], + ) + }; + + let transform_location = + unsafe { gl.get_uniform_location(program, "u_Transform") } + .expect("Get transform location"); + + let scale_location = + unsafe { gl.get_uniform_location(program, "u_Scale") } + .expect("Get scale location"); + + let screen_height_location = + unsafe { gl.get_uniform_location(program, "u_ScreenHeight") } + .expect("Get target height location"); + + unsafe { + gl.use_program(Some(program)); + + let matrix: [f32; 16] = Transformation::identity().into(); + gl.uniform_matrix_4_f32_slice( + Some(&transform_location), + false, + &matrix, + ); + + gl.uniform_1_f32(Some(&scale_location), 1.0); + gl.uniform_1_f32(Some(&screen_height_location), 0.0); + + gl.use_program(None); + } + + let (vertex_array, instances) = + unsafe { create_instance_buffer(gl, MAX_INSTANCES) }; + + Pipeline { + program, + vertex_array, + instances, + transform_location, + scale_location, + screen_height_location, + current_transform: Transformation::identity(), + current_scale: 1.0, + current_target_height: 0, + } + } + + pub fn draw( + &mut self, + gl: &glow::Context, + target_height: u32, + instances: &[layer::Quad], + transformation: Transformation, + scale: f32, + bounds: Rectangle<u32>, + ) { + unsafe { + gl.enable(glow::SCISSOR_TEST); + gl.scissor( + bounds.x as i32, + (target_height - (bounds.y + bounds.height)) as i32, + bounds.width as i32, + bounds.height as i32, + ); + + gl.use_program(Some(self.program)); + gl.bind_vertex_array(Some(self.vertex_array)); + gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.instances)); + } + + if transformation != self.current_transform { + unsafe { + let matrix: [f32; 16] = transformation.into(); + gl.uniform_matrix_4_f32_slice( + Some(&self.transform_location), + false, + &matrix, + ); + + self.current_transform = transformation; + } + } + + if scale != self.current_scale { + unsafe { + gl.uniform_1_f32(Some(&self.scale_location), scale); + } + + self.current_scale = scale; + } + + if target_height != self.current_target_height { + unsafe { + gl.uniform_1_f32( + Some(&self.screen_height_location), + target_height as f32, + ); + } + + self.current_target_height = target_height; + } + + for instances in instances.chunks(MAX_INSTANCES) { + unsafe { + gl.buffer_sub_data_u8_slice( + glow::ARRAY_BUFFER, + 0, + bytemuck::cast_slice(&instances), + ); + + gl.draw_arrays_instanced( + glow::TRIANGLE_STRIP, + 0, + 4, + instances.len() as i32, + ); + } + } + + unsafe { + gl.bind_vertex_array(None); + gl.use_program(None); + gl.disable(glow::SCISSOR_TEST); + } + } +} + +unsafe fn create_instance_buffer( + gl: &glow::Context, + size: usize, +) -> ( + <glow::Context as HasContext>::VertexArray, + <glow::Context as HasContext>::Buffer, +) { + let vertex_array = gl.create_vertex_array().expect("Create vertex array"); + let buffer = gl.create_buffer().expect("Create instance buffer"); + + gl.bind_vertex_array(Some(vertex_array)); + gl.bind_buffer(glow::ARRAY_BUFFER, Some(buffer)); + gl.buffer_data_size( + glow::ARRAY_BUFFER, + (size * std::mem::size_of::<layer::Quad>()) as i32, + glow::DYNAMIC_DRAW, + ); + + let stride = std::mem::size_of::<layer::Quad>() as i32; + + gl.enable_vertex_attrib_array(0); + gl.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, stride, 0); + gl.vertex_attrib_divisor(0, 1); + + gl.enable_vertex_attrib_array(1); + gl.vertex_attrib_pointer_f32(1, 2, glow::FLOAT, false, stride, 4 * 2); + gl.vertex_attrib_divisor(1, 1); + + gl.enable_vertex_attrib_array(2); + gl.vertex_attrib_pointer_f32(2, 4, glow::FLOAT, false, stride, 4 * (2 + 2)); + gl.vertex_attrib_divisor(2, 1); + + gl.enable_vertex_attrib_array(3); + gl.vertex_attrib_pointer_f32( + 3, + 4, + glow::FLOAT, + false, + stride, + 4 * (2 + 2 + 4), + ); + gl.vertex_attrib_divisor(3, 1); + + gl.enable_vertex_attrib_array(4); + gl.vertex_attrib_pointer_f32( + 4, + 1, + glow::FLOAT, + false, + stride, + 4 * (2 + 2 + 4 + 4), + ); + gl.vertex_attrib_divisor(4, 1); + + gl.enable_vertex_attrib_array(5); + gl.vertex_attrib_pointer_f32( + 5, + 1, + glow::FLOAT, + false, + stride, + 4 * (2 + 2 + 4 + 4 + 1), + ); + gl.vertex_attrib_divisor(5, 1); + + gl.bind_vertex_array(None); + gl.bind_buffer(glow::ARRAY_BUFFER, None); + + (vertex_array, buffer) +} diff --git a/glow/src/shader/common/triangle.frag b/glow/src/shader/common/triangle.frag new file mode 100644 index 00000000..e8689f2e --- /dev/null +++ b/glow/src/shader/common/triangle.frag @@ -0,0 +1,18 @@ +#ifdef GL_ES +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif +#endif + +#ifdef HIGHER_THAN_300 +out vec4 fragColor; +#define gl_FragColor fragColor +#endif + +in vec4 v_Color; + +void main() { + gl_FragColor = v_Color; +}
\ No newline at end of file diff --git a/glow/src/shader/triangle.vert b/glow/src/shader/common/triangle.vert index 5723436a..d0494a5f 100644 --- a/glow/src/shader/triangle.vert +++ b/glow/src/shader/common/triangle.vert @@ -1,13 +1,11 @@ -#version 330 - uniform mat4 u_Transform; -layout(location = 0) in vec2 i_Position; -layout(location = 1) in vec4 i_Color; +in vec2 i_Position; +in vec4 i_Color; out vec4 v_Color; void main() { gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0); v_Color = i_Color; -} +}
\ No newline at end of file diff --git a/glow/src/shader/compatibility/quad.frag b/glow/src/shader/compatibility/quad.frag new file mode 100644 index 00000000..8ea5693d --- /dev/null +++ b/glow/src/shader/compatibility/quad.frag @@ -0,0 +1,67 @@ +#ifdef GL_ES +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif +#endif + +uniform float u_ScreenHeight; + +varying vec4 v_Color; +varying vec4 v_BorderColor; +varying vec2 v_Pos; +varying vec2 v_Scale; +varying float v_BorderRadius; +varying float v_BorderWidth; + +float _distance(vec2 frag_coord, vec2 position, vec2 size, float radius) +{ + // TODO: Try SDF approach: https://www.shadertoy.com/view/wd3XRN + vec2 inner_size = size - vec2(radius, radius) * 2.0; + vec2 top_left = position + vec2(radius, radius); + vec2 bottom_right = top_left + inner_size; + + vec2 top_left_distance = top_left - frag_coord; + vec2 bottom_right_distance = frag_coord - bottom_right; + + vec2 distance = vec2( + max(max(top_left_distance.x, bottom_right_distance.x), 0.0), + max(max(top_left_distance.y, bottom_right_distance.y), 0.0) + ); + + return sqrt(distance.x * distance.x + distance.y * distance.y); +} + +void main() { + vec2 fragCoord = vec2(gl_FragCoord.x, u_ScreenHeight - gl_FragCoord.y); + + float internal_border = max(v_BorderRadius - v_BorderWidth, 0.0); + + float internal_distance = _distance( + fragCoord, + v_Pos + vec2(v_BorderWidth), + v_Scale - vec2(v_BorderWidth * 2.0), + internal_border + ); + + float border_mix = smoothstep( + max(internal_border - 0.5, 0.0), + internal_border + 0.5, + internal_distance + ); + + vec4 mixed_color = mix(v_Color, v_BorderColor, border_mix); + + float d = _distance( + fragCoord, + v_Pos, + v_Scale, + v_BorderRadius + ); + + float radius_alpha = + 1.0 - smoothstep(max(v_BorderRadius - 0.5, 0.0), v_BorderRadius + 0.5, d); + + gl_FragColor = vec4(mixed_color.xyz, mixed_color.w * radius_alpha); +} diff --git a/glow/src/shader/compatibility/quad.vert b/glow/src/shader/compatibility/quad.vert new file mode 100644 index 00000000..abe70c0e --- /dev/null +++ b/glow/src/shader/compatibility/quad.vert @@ -0,0 +1,44 @@ +uniform mat4 u_Transform; +uniform float u_Scale; + +attribute vec2 i_Pos; +attribute vec2 i_Scale; +attribute vec4 i_Color; +attribute vec4 i_BorderColor; +attribute float i_BorderRadius; +attribute float i_BorderWidth; +attribute vec2 q_Pos; + +varying vec4 v_Color; +varying vec4 v_BorderColor; +varying vec2 v_Pos; +varying vec2 v_Scale; +varying float v_BorderRadius; +varying float v_BorderWidth; + + +void main() { + vec2 p_Pos = i_Pos * u_Scale; + vec2 p_Scale = i_Scale * u_Scale; + + float i_BorderRadius = min( + i_BorderRadius, + min(i_Scale.x, i_Scale.y) / 2.0 + ); + + mat4 i_Transform = mat4( + vec4(p_Scale.x + 1.0, 0.0, 0.0, 0.0), + vec4(0.0, p_Scale.y + 1.0, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(p_Pos - vec2(0.5, 0.5), 0.0, 1.0) + ); + + v_Color = i_Color; + v_BorderColor = i_BorderColor; + v_Pos = p_Pos; + v_Scale = p_Scale; + v_BorderRadius = i_BorderRadius * u_Scale; + v_BorderWidth = i_BorderWidth * u_Scale; + + gl_Position = u_Transform * i_Transform * vec4(q_Pos, 0.0, 1.0); +} diff --git a/glow/src/shader/quad.frag b/glow/src/shader/core/quad.frag index cea36bdc..57e2e8e7 100644 --- a/glow/src/shader/quad.frag +++ b/glow/src/shader/core/quad.frag @@ -1,4 +1,15 @@ -#version 330 +#ifdef GL_ES +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif +#endif + +#ifdef HIGHER_THAN_300 +out vec4 fragColor; +#define gl_FragColor fragColor +#endif uniform float u_ScreenHeight; @@ -9,9 +20,7 @@ in vec2 v_Scale; in float v_BorderRadius; in float v_BorderWidth; -out vec4 o_Color; - -float distance(in vec2 frag_coord, in vec2 position, in vec2 size, float radius) +float fDistance(vec2 frag_coord, vec2 position, vec2 size, float radius) { // TODO: Try SDF approach: https://www.shadertoy.com/view/wd3XRN vec2 inner_size = size - vec2(radius, radius) * 2.0; @@ -35,10 +44,10 @@ void main() { vec2 fragCoord = vec2(gl_FragCoord.x, u_ScreenHeight - gl_FragCoord.y); // TODO: Remove branching (?) - if(v_BorderWidth > 0) { + if(v_BorderWidth > 0.0) { float internal_border = max(v_BorderRadius - v_BorderWidth, 0.0); - float internal_distance = distance( + float internal_distance = fDistance( fragCoord, v_Pos + vec2(v_BorderWidth), v_Scale - vec2(v_BorderWidth * 2.0), @@ -56,7 +65,7 @@ void main() { mixed_color = v_Color; } - float d = distance( + float d = fDistance( fragCoord, v_Pos, v_Scale, @@ -66,5 +75,5 @@ void main() { float radius_alpha = 1.0 - smoothstep(max(v_BorderRadius - 0.5, 0.0), v_BorderRadius + 0.5, d); - o_Color = vec4(mixed_color.xyz, mixed_color.w * radius_alpha); + gl_FragColor = vec4(mixed_color.xyz, mixed_color.w * radius_alpha); } diff --git a/glow/src/shader/quad.vert b/glow/src/shader/core/quad.vert index 82417856..b1fb2365 100644 --- a/glow/src/shader/quad.vert +++ b/glow/src/shader/core/quad.vert @@ -1,14 +1,12 @@ -#version 330 - uniform mat4 u_Transform; uniform float u_Scale; -layout(location = 0) in vec2 i_Pos; -layout(location = 1) in vec2 i_Scale; -layout(location = 2) in vec4 i_Color; -layout(location = 3) in vec4 i_BorderColor; -layout(location = 4) in float i_BorderRadius; -layout(location = 5) in float i_BorderWidth; +in vec2 i_Pos; +in vec2 i_Scale; +in vec4 i_Color; +in vec4 i_BorderColor; +in float i_BorderRadius; +in float i_BorderWidth; out vec4 v_Color; out vec4 v_BorderColor; @@ -17,7 +15,7 @@ out vec2 v_Scale; out float v_BorderRadius; out float v_BorderWidth; -const vec2 positions[4] = vec2[]( +vec2 positions[4] = vec2[]( vec2(0.0, 0.0), vec2(0.0, 1.0), vec2(1.0, 0.0), diff --git a/glow/src/shader/triangle.frag b/glow/src/shader/triangle.frag deleted file mode 100644 index d186784a..00000000 --- a/glow/src/shader/triangle.frag +++ /dev/null @@ -1,9 +0,0 @@ -#version 330 - -in vec4 v_Color; - -out vec4 o_Color; - -void main() { - o_Color = v_Color; -} diff --git a/glow/src/text.rs b/glow/src/text.rs index 3b6f3bf5..0d45d61b 100644 --- a/glow/src/text.rs +++ b/glow/src/text.rs @@ -46,11 +46,15 @@ impl Pipeline { .expect("Load fallback font") }); - let draw_brush = + let draw_brush_builder = glow_glyph::GlyphBrushBuilder::using_font(font.clone()) .initial_cache_size((2048, 2048)) - .draw_cache_multithread(multithreading) - .build(&gl); + .draw_cache_multithread(multithreading); + + #[cfg(target_arch = "wasm32")] + let draw_brush_builder = draw_brush_builder.draw_cache_align_4x4(true); + + let draw_brush = draw_brush_builder.build(&gl); let measure_brush = glyph_brush::GlyphBrushBuilder::using_font(font).build(); diff --git a/glow/src/triangle.rs b/glow/src/triangle.rs index 9202bcb2..ae4f83ef 100644 --- a/glow/src/triangle.rs +++ b/glow/src/triangle.rs @@ -1,5 +1,5 @@ //! Draw meshes of triangles. -use crate::program; +use crate::program::{self, Shader}; use crate::Transformation; use glow::HasContext; use iced_graphics::layer; @@ -21,17 +21,26 @@ pub(crate) struct Pipeline { } impl Pipeline { - pub fn new(gl: &glow::Context) -> Pipeline { + pub fn new( + gl: &glow::Context, + shader_version: &program::Version, + ) -> Pipeline { let program = unsafe { + let vertex_shader = Shader::vertex( + gl, + shader_version, + include_str!("shader/common/triangle.vert"), + ); + let fragment_shader = Shader::fragment( + gl, + shader_version, + include_str!("shader/common/triangle.frag"), + ); + program::create( gl, - &[ - (glow::VERTEX_SHADER, include_str!("shader/triangle.vert")), - ( - glow::FRAGMENT_SHADER, - include_str!("shader/triangle.frag"), - ), - ], + &[vertex_shader, fragment_shader], + &[(0, "i_Position"), (1, "i_Color")], ) }; diff --git a/glow/src/window/compositor.rs b/glow/src/window/compositor.rs index a85a4560..44019fb2 100644 --- a/glow/src/window/compositor.rs +++ b/glow/src/window/compositor.rs @@ -20,6 +20,13 @@ impl iced_graphics::window::GLCompositor for Compositor { ) -> Result<(Self, Self::Renderer), Error> { let gl = glow::Context::from_loader_function(loader_function); + let version = gl.version(); + log::info!("Version: {:?}", version); + log::info!("Embedded: {}", version.is_embedded); + + let renderer = gl.get_parameter_string(glow::RENDERER); + log::info!("Renderer: {}", renderer); + // Enable auto-conversion from/to sRGB gl.enable(glow::FRAMEBUFFER_SRGB); diff --git a/glutin/Cargo.toml b/glutin/Cargo.toml index 52efe66c..d1b0468d 100644 --- a/glutin/Cargo.toml +++ b/glutin/Cargo.toml @@ -2,7 +2,7 @@ name = "iced_glutin" version = "0.2.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" description = "A glutin runtime for Iced" license = "MIT" repository = "https://github.com/iced-rs/iced" diff --git a/glutin/README.md b/glutin/README.md index bbc2d5e8..263cc0af 100644 --- a/glutin/README.md +++ b/glutin/README.md @@ -2,7 +2,7 @@ [][documentation] [](https://crates.io/crates/iced_glutin) [](https://github.com/iced-rs/iced/blob/master/LICENSE) -[](https://iced.zulipchat.com) +[](https://discord.gg/3xZJ65GAhd) `iced_glutin` offers some convenient abstractions on top of [`iced_native`] to quickstart development when using [`glutin`]. diff --git a/glutin/src/application.rs b/glutin/src/application.rs index 437c17ee..27a932fc 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -61,10 +61,23 @@ where settings.id, ); - let context = ContextBuilder::new() + let opengl_builder = ContextBuilder::new() .with_vsync(true) - .with_multisampling(C::sample_count(&compositor_settings) as u16) - .build_windowed(builder, &event_loop) + .with_multisampling(C::sample_count(&compositor_settings) as u16); + + let opengles_builder = opengl_builder.clone().with_gl( + glutin::GlRequest::Specific(glutin::Api::OpenGlEs, (2, 0)), + ); + + let (first_builder, second_builder) = if settings.try_opengles_first { + (opengles_builder, opengl_builder) + } else { + (opengl_builder, opengles_builder) + }; + + let context = first_builder + .build_windowed(builder.clone(), &event_loop) + .or_else(|_| second_builder.build_windowed(builder, &event_loop)) .map_err(|error| { use glutin::CreationError; diff --git a/glutin/src/lib.rs b/glutin/src/lib.rs index 224296b7..72397791 100644 --- a/glutin/src/lib.rs +++ b/glutin/src/lib.rs @@ -20,6 +20,7 @@ pub use iced_native::*; pub mod application; pub use iced_winit::clipboard; +pub use iced_winit::conversion; pub use iced_winit::settings; pub use iced_winit::window; pub use iced_winit::{Error, Mode}; diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index 12f38cce..8ccc7849 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -2,7 +2,7 @@ name = "iced_graphics" version = "0.2.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" description = "A bunch of backend-agnostic types that can be leveraged to build a renderer for Iced" license = "MIT" repository = "https://github.com/hecrj/iced" diff --git a/graphics/src/widget/canvas.rs b/graphics/src/widget/canvas.rs index f9722f33..65d7e37e 100644 --- a/graphics/src/widget/canvas.rs +++ b/graphics/src/widget/canvas.rs @@ -9,10 +9,9 @@ use crate::{Backend, Primitive}; use iced_native::layout; use iced_native::mouse; use iced_native::{ - Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Shell, Size, - Vector, Widget, + Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector, + Widget, }; -use std::hash::Hash; use std::marker::PhantomData; pub mod event; @@ -35,7 +34,7 @@ pub use frame::Frame; pub use geometry::Geometry; pub use path::Path; pub use program::Program; -pub use stroke::{LineCap, LineJoin, Stroke}; +pub use stroke::{LineCap, LineDash, LineJoin, Stroke}; pub use text::Text; /// A widget capable of drawing 2D graphics. @@ -212,6 +211,11 @@ where use iced_native::Renderer as _; let bounds = layout.bounds(); + + if bounds.width < 1.0 || bounds.height < 1.0 { + return; + } + let translation = Vector::new(bounds.x, bounds.y); let cursor = Cursor::from_window_position(cursor_position); @@ -226,14 +230,6 @@ where }); }); } - - fn hash_layout(&self, state: &mut Hasher) { - struct Marker; - std::any::TypeId::of::<Marker>().hash(state); - - self.width.hash(state); - self.height.hash(state); - } } impl<'a, Message, P, B> From<Canvas<Message, P>> diff --git a/graphics/src/widget/canvas/frame.rs b/graphics/src/widget/canvas/frame.rs index 08518f40..a3449605 100644 --- a/graphics/src/widget/canvas/frame.rs +++ b/graphics/src/widget/canvas/frame.rs @@ -1,6 +1,9 @@ +use std::borrow::Cow; + use iced_native::{Point, Rectangle, Size, Vector}; use crate::{ + canvas::path, canvas::{Fill, Geometry, Path, Stroke, Text}, triangle, Primitive, }; @@ -150,7 +153,7 @@ impl Frame { /// Draws the stroke of the given [`Path`] on the [`Frame`] with the /// provided style. - pub fn stroke(&mut self, path: &Path, stroke: impl Into<Stroke>) { + pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) { let stroke = stroke.into(); let mut buffers = tessellation::BuffersBuilder::new( @@ -164,6 +167,12 @@ impl Frame { options.end_cap = stroke.line_cap.into(); options.line_join = stroke.line_join.into(); + let path = if stroke.line_dash.segments.is_empty() { + Cow::Borrowed(path) + } else { + Cow::Owned(path::dashed(path, stroke.line_dash)) + }; + let result = if self.transforms.current.is_identity { self.stroke_tessellator.tessellate_path( path.raw(), diff --git a/graphics/src/widget/canvas/path.rs b/graphics/src/widget/canvas/path.rs index 4e4fd734..1728f060 100644 --- a/graphics/src/widget/canvas/path.rs +++ b/graphics/src/widget/canvas/path.rs @@ -7,7 +7,11 @@ mod builder; pub use arc::Arc; pub use builder::Builder; +use crate::canvas::LineDash; + use iced_native::{Point, Size}; +use lyon::algorithms::walk::{walk_along_path, RepeatedPattern}; +use lyon::path::iterator::PathIterator; /// An immutable set of points that may or may not be connected. /// @@ -66,3 +70,43 @@ impl Path { } } } + +pub(super) fn dashed(path: &Path, line_dash: LineDash<'_>) -> Path { + Path::new(|builder| { + let segments_odd = (line_dash.segments.len() % 2 == 1).then(|| { + [&line_dash.segments[..], &line_dash.segments[..]].concat() + }); + + let mut draw_line = false; + + walk_along_path( + path.raw().iter().flattened(0.01), + 0.0, + &mut RepeatedPattern { + callback: |position: lyon::algorithms::math::Point, + _tangent, + _distance| { + let point = Point { + x: position.x, + y: position.y, + }; + + if draw_line { + builder.line_to(point); + } else { + builder.move_to(point); + } + + draw_line = !draw_line; + + true + }, + index: line_dash.offset, + intervals: segments_odd + .as_ref() + .map(Vec::as_slice) + .unwrap_or(line_dash.segments), + }, + ); + }) +} diff --git a/graphics/src/widget/canvas/stroke.rs b/graphics/src/widget/canvas/stroke.rs index 9f0449d0..6accc2fb 100644 --- a/graphics/src/widget/canvas/stroke.rs +++ b/graphics/src/widget/canvas/stroke.rs @@ -2,7 +2,7 @@ use iced_native::Color; /// The style of a stroke. #[derive(Debug, Clone, Copy)] -pub struct Stroke { +pub struct Stroke<'a> { /// The color of the stroke. pub color: Color, /// The distance between the two edges of the stroke. @@ -12,37 +12,40 @@ pub struct Stroke { /// The shape to be used at the corners of paths or basic shapes when they /// are stroked. pub line_join: LineJoin, + /// The dash pattern used when stroking the line. + pub line_dash: LineDash<'a>, } -impl Stroke { +impl<'a> Stroke<'a> { /// Sets the color of the [`Stroke`]. - pub fn with_color(self, color: Color) -> Stroke { + pub fn with_color(self, color: Color) -> Self { Stroke { color, ..self } } /// Sets the width of the [`Stroke`]. - pub fn with_width(self, width: f32) -> Stroke { + pub fn with_width(self, width: f32) -> Self { Stroke { width, ..self } } /// Sets the [`LineCap`] of the [`Stroke`]. - pub fn with_line_cap(self, line_cap: LineCap) -> Stroke { + pub fn with_line_cap(self, line_cap: LineCap) -> Self { Stroke { line_cap, ..self } } /// Sets the [`LineJoin`] of the [`Stroke`]. - pub fn with_line_join(self, line_join: LineJoin) -> Stroke { + pub fn with_line_join(self, line_join: LineJoin) -> Self { Stroke { line_join, ..self } } } -impl Default for Stroke { - fn default() -> Stroke { +impl<'a> Default for Stroke<'a> { + fn default() -> Self { Stroke { color: Color::BLACK, width: 1.0, line_cap: LineCap::default(), line_join: LineJoin::default(), + line_dash: LineDash::default(), } } } @@ -103,3 +106,13 @@ impl From<LineJoin> for lyon::tessellation::LineJoin { } } } + +/// The dash pattern used when stroking the line. +#[derive(Debug, Clone, Copy, Default)] +pub struct LineDash<'a> { + /// The alternating lengths of lines and gaps which describe the pattern. + pub segments: &'a [f32], + + /// The offset of [`LineDash::segments`] to start the pattern. + pub offset: usize, +} diff --git a/graphics/src/widget/qr_code.rs b/graphics/src/widget/qr_code.rs index 285b8622..907794b7 100644 --- a/graphics/src/widget/qr_code.rs +++ b/graphics/src/widget/qr_code.rs @@ -5,8 +5,7 @@ use crate::Backend; use iced_native::layout; use iced_native::{ - Color, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector, - Widget, + Color, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget, }; use thiserror::Error; @@ -74,12 +73,6 @@ where )) } - fn hash_layout(&self, state: &mut Hasher) { - use std::hash::Hash; - - self.state.contents.hash(state); - } - fn draw( &self, renderer: &mut Renderer<B>, diff --git a/lazy/src/component.rs b/lazy/src/component.rs index 00c27989..9e5937e9 100644 --- a/lazy/src/component.rs +++ b/lazy/src/component.rs @@ -7,12 +7,11 @@ use iced_native::mouse; use iced_native::overlay; use iced_native::renderer; use iced_native::{ - Clipboard, Element, Hasher, Length, Point, Rectangle, Shell, Size, Widget, + Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget, }; use ouroboros::self_referencing; use std::cell::RefCell; -use std::hash::Hash; use std::marker::PhantomData; /// A reusable, custom widget that uses The Elm Architecture. @@ -217,12 +216,6 @@ where }); } - fn hash_layout(&self, state: &mut Hasher) { - self.with_element(|element| { - element.hash_layout(state); - }); - } - fn mouse_interaction( &self, layout: Layout<'_>, @@ -371,18 +364,6 @@ where .unwrap_or_default() } - fn hash_layout(&self, state: &mut Hasher, position: Point) { - struct Marker; - std::any::TypeId::of::<Marker>().hash(state); - - (position.x as u32).hash(state); - (position.y as u32).hash(state); - - self.with_overlay_maybe(|overlay| { - overlay.hash_layout(state); - }); - } - fn on_event( &mut self, event: iced_native::Event, diff --git a/lazy/src/responsive.rs b/lazy/src/responsive.rs index 35ad6974..20a80dac 100644 --- a/lazy/src/responsive.rs +++ b/lazy/src/responsive.rs @@ -8,11 +8,10 @@ use iced_native::overlay; use iced_native::renderer; use iced_native::window; use iced_native::{ - Clipboard, Element, Hasher, Length, Point, Rectangle, Shell, Size, Widget, + Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget, }; use std::cell::RefCell; -use std::hash::{Hash, Hasher as _}; use std::ops::Deref; /// The state of a [`Responsive`] widget. @@ -20,7 +19,6 @@ use std::ops::Deref; pub struct State { last_size: Option<Size>, last_layout: layout::Node, - last_layout_hash: u64, } impl State { @@ -76,8 +74,6 @@ where Length::Fill } - fn hash_layout(&self, _hasher: &mut Hasher) {} - fn layout( &self, _renderer: &Renderer, @@ -178,7 +174,7 @@ where let content_layout = state.layout(layout); match content { - Content::Pending(_) => false, + Content::Pending(_) => None, Content::Ready(cache) => { *cache = Some( CacheBuilder { @@ -190,14 +186,19 @@ where .build(), ); - cache.as_ref().unwrap().borrow_overlay().is_some() + cache + .as_ref() + .unwrap() + .borrow_overlay() + .as_ref() + .map(|overlay| overlay.position()) } } }; - has_overlay.then(|| { + has_overlay.map(|position| { overlay::Element::new( - layout.position(), + position, Box::new(Overlay { instance: self }), ) }) @@ -265,26 +266,13 @@ where let element = view.take().unwrap()(state.last_size.unwrap_or(Size::ZERO)); - let new_layout_hash = { - let mut hasher = Hasher::default(); - element.hash_layout(&mut hasher); - - hasher.finish() - }; - - if state.last_size != Some(state.last_layout.size()) - || new_layout_hash != state.last_layout_hash - { - state.last_layout = element.layout( - renderer.deref(), - &layout::Limits::new( - Size::ZERO, - state.last_size.unwrap_or(Size::ZERO), - ), - ); - - state.last_layout_hash = new_layout_hash; - } + state.last_layout = element.layout( + renderer.deref(), + &layout::Limits::new( + Size::ZERO, + state.last_size.unwrap_or(Size::ZERO), + ), + ); *self = Content::Ready(Some( CacheBuilder { @@ -395,18 +383,6 @@ where .unwrap_or_default() } - fn hash_layout(&self, state: &mut Hasher, position: Point) { - struct Marker; - std::any::TypeId::of::<Marker>().hash(state); - - (position.x as u32).hash(state); - (position.y as u32).hash(state); - - self.with_overlay_maybe(|overlay| { - overlay.hash_layout(state); - }); - } - fn on_event( &mut self, event: iced_native::Event, diff --git a/native/Cargo.toml b/native/Cargo.toml index 8f0aea6a..c4b363ae 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -2,7 +2,7 @@ name = "iced_native" version = "0.4.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" description = "A renderer-agnostic library for native GUIs" license = "MIT" repository = "https://github.com/iced-rs/iced" diff --git a/native/README.md b/native/README.md index ceb525d9..c1e160c7 100644 --- a/native/README.md +++ b/native/README.md @@ -2,7 +2,7 @@ [][documentation] [](https://crates.io/crates/iced_native) [](https://github.com/iced-rs/iced/blob/master/LICENSE) -[](https://iced.zulipchat.com) +[](https://discord.gg/3xZJ65GAhd) `iced_native` takes [`iced_core`] and builds a native runtime on top of it, featuring: - A custom layout engine, greatly inspired by [`druid`] diff --git a/native/src/clipboard.rs b/native/src/clipboard.rs index 60703c31..c9105bc0 100644 --- a/native/src/clipboard.rs +++ b/native/src/clipboard.rs @@ -1,4 +1,6 @@ //! Access the clipboard. +use iced_futures::MaybeSend; + use std::fmt; /// A buffer for short-term storage and transfer within and between @@ -36,7 +38,10 @@ pub enum Action<T> { impl<T> Action<T> { /// Maps the output of a clipboard [`Action`] using the provided closure. - pub fn map<A>(self, f: impl Fn(T) -> A + 'static + Send + Sync) -> Action<A> + pub fn map<A>( + self, + f: impl Fn(T) -> A + 'static + MaybeSend + Sync, + ) -> Action<A> where T: 'static, { diff --git a/native/src/command.rs b/native/src/command.rs index 6fe518d7..89d0f045 100644 --- a/native/src/command.rs +++ b/native/src/command.rs @@ -3,6 +3,8 @@ mod action; pub use action::Action; +use iced_futures::MaybeSend; + use std::fmt; use std::future::Future; @@ -24,8 +26,8 @@ impl<T> Command<T> { /// Creates a [`Command`] that performs the action of the given future. pub fn perform<A>( - future: impl Future<Output = T> + 'static + Send, - f: impl Fn(T) -> A + 'static + Send, + future: impl Future<Output = T> + 'static + MaybeSend, + f: impl Fn(T) -> A + 'static + MaybeSend, ) -> Command<A> { use iced_futures::futures::FutureExt; @@ -45,7 +47,7 @@ impl<T> Command<T> { /// Applies a transformation to the result of a [`Command`]. pub fn map<A>( self, - f: impl Fn(T) -> A + 'static + Send + Sync + Clone, + f: impl Fn(T) -> A + 'static + MaybeSend + Sync + Clone, ) -> Command<A> where T: 'static, diff --git a/native/src/command/action.rs b/native/src/command/action.rs index 77be1b59..5c7509c8 100644 --- a/native/src/command/action.rs +++ b/native/src/command/action.rs @@ -1,6 +1,8 @@ use crate::clipboard; use crate::window; +use iced_futures::MaybeSend; + use std::fmt; /// An action that a [`Command`] can perform. @@ -19,7 +21,10 @@ pub enum Action<T> { impl<T> Action<T> { /// Applies a transformation to the result of a [`Command`]. - pub fn map<A>(self, f: impl Fn(T) -> A + 'static + Send + Sync) -> Action<A> + pub fn map<A>( + self, + f: impl Fn(T) -> A + 'static + MaybeSend + Sync, + ) -> Action<A> where T: 'static, { diff --git a/native/src/debug/basic.rs b/native/src/debug/basic.rs index a42f66ea..d706bb00 100644 --- a/native/src/debug/basic.rs +++ b/native/src/debug/basic.rs @@ -1,5 +1,7 @@ #![allow(missing_docs)] -use std::{collections::VecDeque, time}; +use crate::time; + +use std::collections::VecDeque; /// A bunch of time measurements for debugging purposes. #[derive(Debug)] diff --git a/native/src/element.rs b/native/src/element.rs index 6afa3f62..119b7892 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -4,7 +4,7 @@ use crate::mouse; use crate::overlay; use crate::renderer; use crate::{ - Clipboard, Color, Hasher, Layout, Length, Point, Rectangle, Shell, Widget, + Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Widget, }; /// A generic [`Widget`]. @@ -269,11 +269,6 @@ where ) } - /// Computes the _layout_ hash of the [`Element`]. - pub fn hash_layout(&self, state: &mut Hasher) { - self.widget.hash_layout(state); - } - /// Returns the overlay of the [`Element`], if there is any. pub fn overlay<'b>( &'b mut self, @@ -379,10 +374,6 @@ where ) } - fn hash_layout(&self, state: &mut Hasher) { - self.widget.hash_layout(state); - } - fn overlay( &mut self, layout: Layout<'_>, @@ -504,10 +495,6 @@ where ) } - fn hash_layout(&self, state: &mut Hasher) { - self.element.widget.hash_layout(state); - } - fn overlay( &mut self, layout: Layout<'_>, diff --git a/native/src/lib.rs b/native/src/lib.rs index a5526e6d..5c9c24c9 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -69,9 +69,10 @@ mod debug; mod debug; pub use iced_core::alignment; +pub use iced_core::time; pub use iced_core::{ - Alignment, Background, Color, Font, Length, Padding, Point, Rectangle, - Size, Vector, + Alignment, Background, Color, ContentFit, Font, Length, Padding, Point, + Rectangle, Size, Vector, }; pub use iced_futures::{executor, futures}; diff --git a/native/src/mouse/click.rs b/native/src/mouse/click.rs index 6c8b61a5..4a7d796c 100644 --- a/native/src/mouse/click.rs +++ b/native/src/mouse/click.rs @@ -1,6 +1,6 @@ //! Track mouse clicks. +use crate::time::Instant; use crate::Point; -use std::time::Instant; /// A mouse click. #[derive(Debug, Clone, Copy)] @@ -62,9 +62,14 @@ impl Click { } fn is_consecutive(&self, new_position: Point, time: Instant) -> bool { + let duration = if time > self.time { + Some(time - self.time) + } else { + None + }; + self.position == new_position - && time - .checked_duration_since(self.time) + && duration .map(|duration| duration.as_millis() <= 300) .unwrap_or(false) } diff --git a/native/src/overlay.rs b/native/src/overlay.rs index d4b641af..124bcac2 100644 --- a/native/src/overlay.rs +++ b/native/src/overlay.rs @@ -10,7 +10,7 @@ use crate::event::{self, Event}; use crate::layout; use crate::mouse; use crate::renderer; -use crate::{Clipboard, Hasher, Layout, Point, Rectangle, Shell, Size}; +use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size}; /// An interactive component that can be displayed on top of other widgets. pub trait Overlay<Message, Renderer> @@ -39,19 +39,6 @@ where cursor_position: Point, ); - /// Computes the _layout_ hash of the [`Overlay`]. - /// - /// The produced hash is used by the runtime to decide if the [`Layout`] - /// needs to be recomputed between frames. Therefore, to ensure maximum - /// efficiency, the hash should only be affected by the properties of the - /// [`Overlay`] that can affect layouting. - /// - /// For example, the [`Text`] widget does not hash its color property, as - /// its value cannot affect the overall [`Layout`] of the user interface. - /// - /// [`Text`]: crate::widget::Text - fn hash_layout(&self, state: &mut Hasher, position: Point); - /// Processes a runtime [`Event`]. /// /// It receives: diff --git a/native/src/overlay/element.rs b/native/src/overlay/element.rs index e7621600..b60881e3 100644 --- a/native/src/overlay/element.rs +++ b/native/src/overlay/element.rs @@ -4,7 +4,7 @@ use crate::event::{self, Event}; use crate::layout; use crate::mouse; use crate::renderer; -use crate::{Clipboard, Hasher, Layout, Point, Rectangle, Shell, Size, Vector}; +use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size, Vector}; /// A generic [`Overlay`]. #[allow(missing_debug_implementations)] @@ -100,11 +100,6 @@ where ) { self.overlay.draw(renderer, style, layout, cursor_position) } - - /// Computes the _layout_ hash of the [`Element`]. - pub fn hash_layout(&self, state: &mut Hasher) { - self.overlay.hash_layout(state, self.position); - } } struct Map<'a, A, B, Renderer> { @@ -184,8 +179,4 @@ where ) { self.content.draw(renderer, style, layout, cursor_position) } - - fn hash_layout(&self, state: &mut Hasher, position: Point) { - self.content.hash_layout(state, position); - } } diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index 2deef551..13fa7beb 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -10,8 +10,8 @@ use crate::touch; use crate::widget::scrollable::{self, Scrollable}; use crate::widget::Container; use crate::{ - Clipboard, Color, Element, Hasher, Layout, Length, Padding, Point, - Rectangle, Shell, Size, Vector, Widget, + Clipboard, Color, Element, Layout, Length, Padding, Point, Rectangle, + Shell, Size, Vector, Widget, }; pub use iced_style::menu::Style; @@ -204,17 +204,6 @@ where node } - fn hash_layout(&self, state: &mut Hasher, position: Point) { - use std::hash::Hash; - - struct Marker; - std::any::TypeId::of::<Marker>().hash(state); - - (position.x as u32).hash(state); - (position.y as u32).hash(state); - self.container.hash_layout(state); - } - fn on_event( &mut self, event: Event, @@ -320,17 +309,6 @@ where layout::Node::new(size) } - fn hash_layout(&self, state: &mut Hasher) { - use std::hash::Hash as _; - - struct Marker; - std::any::TypeId::of::<Marker>().hash(state); - - self.options.len().hash(state); - self.text_size.hash(state); - self.padding.hash(state); - } - fn on_event( &mut self, event: Event, diff --git a/native/src/subscription.rs b/native/src/subscription.rs index 63834654..9775c84b 100644 --- a/native/src/subscription.rs +++ b/native/src/subscription.rs @@ -3,7 +3,7 @@ use crate::event::{self, Event}; use crate::Hasher; use iced_futures::futures::{self, Future, Stream}; -use iced_futures::BoxStream; +use iced_futures::{BoxStream, MaybeSend}; use std::hash::Hash; @@ -56,7 +56,7 @@ pub fn events_with<Message>( f: fn(Event, event::Status) -> Option<Message>, ) -> Subscription<Message> where - Message: 'static + Send, + Message: 'static + MaybeSend, { Subscription::from_recipe(Runner { id: f, @@ -78,7 +78,7 @@ where pub fn run<I, S, Message>(id: I, stream: S) -> Subscription<Message> where I: Hash + 'static, - S: Stream<Item = Message> + Send + 'static, + S: Stream<Item = Message> + MaybeSend + 'static, Message: 'static, { Subscription::from_recipe(Runner { @@ -159,13 +159,13 @@ where pub fn unfold<I, T, Fut, Message>( id: I, initial: T, - mut f: impl FnMut(T) -> Fut + Send + Sync + 'static, + mut f: impl FnMut(T) -> Fut + MaybeSend + Sync + 'static, ) -> Subscription<Message> where I: Hash + 'static, - T: Send + 'static, - Fut: Future<Output = (Option<Message>, T)> + Send + 'static, - Message: 'static + Send, + T: MaybeSend + 'static, + Fut: Future<Output = (Option<Message>, T)> + MaybeSend + 'static, + Message: 'static + MaybeSend, { use futures::future::{self, FutureExt}; use futures::stream::StreamExt; @@ -191,7 +191,7 @@ impl<I, S, F, Message> Recipe<Hasher, (Event, event::Status)> where I: Hash + 'static, F: FnOnce(EventStream) -> S, - S: Stream<Item = Message> + Send + 'static, + S: Stream<Item = Message> + MaybeSend + 'static, { type Output = Message; @@ -201,8 +201,6 @@ where } fn stream(self: Box<Self>, input: EventStream) -> BoxStream<Self::Output> { - use futures::stream::StreamExt; - - (self.spawn)(input).boxed() + iced_futures::boxed_stream((self.spawn)(input)) } } diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index f6ec96bb..6fc6a479 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -2,12 +2,9 @@ use crate::event::{self, Event}; use crate::layout; use crate::mouse; -use crate::overlay; use crate::renderer; use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size}; -use std::hash::Hasher; - /// A set of interactive graphical elements with a specific [`Layout`]. /// /// It can be updated and drawn. @@ -23,8 +20,8 @@ use std::hash::Hasher; #[allow(missing_debug_implementations)] pub struct UserInterface<'a, Message, Renderer> { root: Element<'a, Message, Renderer>, - base: Layer, - overlay: Option<Layer>, + base: layout::Node, + overlay: Option<layout::Node>, bounds: Size, } @@ -89,41 +86,18 @@ where pub fn build<E: Into<Element<'a, Message, Renderer>>>( root: E, bounds: Size, - cache: Cache, + _cache: Cache, renderer: &mut Renderer, ) -> Self { let root = root.into(); - let (base, overlay) = { - let hash = { - let hasher = &mut crate::Hasher::default(); - root.hash_layout(hasher); - - hasher.finish() - }; - - let layout_is_cached = - hash == cache.base.hash && bounds == cache.bounds; - - let (layout, overlay) = if layout_is_cached { - (cache.base.layout, cache.overlay) - } else { - ( - renderer.layout( - &root, - &layout::Limits::new(Size::ZERO, bounds), - ), - None, - ) - }; - - (Layer { layout, hash }, overlay) - }; + let base = + renderer.layout(&root, &layout::Limits::new(Size::ZERO, bounds)); UserInterface { root, base, - overlay, + overlay: None, bounds, } } @@ -206,16 +180,10 @@ where let mut state = State::Updated; let (base_cursor, overlay_statuses) = if let Some(mut overlay) = - self.root.overlay(Layout::new(&self.base.layout), renderer) + self.root.overlay(Layout::new(&self.base), renderer) { let bounds = self.bounds; - - let mut layer = Self::overlay_layer( - self.overlay.take(), - bounds, - &mut overlay, - renderer, - ); + let mut layout = overlay.layout(renderer, bounds); let event_statuses = events .iter() @@ -225,7 +193,7 @@ where let event_status = overlay.on_event( event, - Layout::new(&layer.layout), + Layout::new(&layout), cursor_position, renderer, clipboard, @@ -233,12 +201,7 @@ where ); shell.revalidate_layout(|| { - layer = Self::overlay_layer( - None, - bounds, - &mut overlay, - renderer, - ); + layout = overlay.layout(renderer, bounds); }); if shell.are_widgets_invalid() { @@ -249,15 +212,14 @@ where }) .collect(); - let base_cursor = if layer.layout.bounds().contains(cursor_position) - { + let base_cursor = if layout.bounds().contains(cursor_position) { // TODO: Type-safe cursor availability Point::new(-1.0, -1.0) } else { cursor_position }; - self.overlay = Some(layer); + self.overlay = Some(layout); (base_cursor, event_statuses) } else { @@ -273,7 +235,7 @@ where let event_status = self.root.widget.on_event( event, - Layout::new(&self.base.layout), + Layout::new(&self.base), base_cursor, renderer, clipboard, @@ -281,19 +243,11 @@ where ); shell.revalidate_layout(|| { - let hash = { - let hasher = &mut crate::Hasher::default(); - self.root.hash_layout(hasher); - - hasher.finish() - }; - - let layout = renderer.layout( + self.base = renderer.layout( &self.root, &layout::Limits::new(Size::ZERO, self.bounds), ); - self.base = Layer { layout, hash }; self.overlay = None; }); @@ -391,48 +345,38 @@ where let viewport = Rectangle::with_size(self.bounds); - self.overlay = if let Some(mut overlay) = - self.root.overlay(Layout::new(&self.base.layout), renderer) + let base_cursor = if let Some(overlay) = + self.root.overlay(Layout::new(&self.base), renderer) { - let layer = Self::overlay_layer( - self.overlay.take(), - self.bounds, - &mut overlay, - renderer, - ); - - Some(layer) - } else { - None - }; + let overlay_layout = self + .overlay + .take() + .unwrap_or_else(|| overlay.layout(renderer, self.bounds)); - if let Some(layer) = &self.overlay { - let base_cursor = if layer.layout.bounds().contains(cursor_position) - { - Point::new(-1.0, -1.0) - } else { - cursor_position - }; + let new_cursor_position = + if overlay_layout.bounds().contains(cursor_position) { + Point::new(-1.0, -1.0) + } else { + cursor_position + }; + + self.overlay = Some(overlay_layout); - self.root.widget.draw( - renderer, - &renderer::Style::default(), - Layout::new(&self.base.layout), - base_cursor, - &viewport, - ); + new_cursor_position } else { - self.root.widget.draw( - renderer, - &renderer::Style::default(), - Layout::new(&self.base.layout), - cursor_position, - &viewport, - ); + cursor_position }; + self.root.widget.draw( + renderer, + &renderer::Style::default(), + Layout::new(&self.base), + base_cursor, + &viewport, + ); + let base_interaction = self.root.widget.mouse_interaction( - Layout::new(&self.base.layout), + Layout::new(&self.base), cursor_position, &viewport, renderer, @@ -452,34 +396,32 @@ where // avoid this additional call. overlay .as_ref() - .and_then(|layer| { - root.overlay(Layout::new(&base.layout), renderer).map( - |overlay| { - let overlay_interaction = overlay.mouse_interaction( - Layout::new(&layer.layout), - cursor_position, - &viewport, + .and_then(|layout| { + root.overlay(Layout::new(&base), renderer).map(|overlay| { + let overlay_interaction = overlay.mouse_interaction( + Layout::new(layout), + cursor_position, + &viewport, + renderer, + ); + + let overlay_bounds = layout.bounds(); + + renderer.with_layer(overlay_bounds, |renderer| { + overlay.draw( renderer, + &renderer::Style::default(), + Layout::new(layout), + cursor_position, ); + }); - let overlay_bounds = layer.layout.bounds(); - - renderer.with_layer(overlay_bounds, |renderer| { - overlay.draw( - renderer, - &renderer::Style::default(), - Layout::new(&layer.layout), - cursor_position, - ); - }); - - if overlay_bounds.contains(cursor_position) { - overlay_interaction - } else { - base_interaction - } - }, - ) + if overlay_bounds.contains(cursor_position) { + overlay_interaction + } else { + base_interaction + } + }) }) .unwrap_or(base_interaction) } @@ -487,66 +429,19 @@ where /// Relayouts and returns a new [`UserInterface`] using the provided /// bounds. pub fn relayout(self, bounds: Size, renderer: &mut Renderer) -> Self { - Self::build( - self.root, - bounds, - Cache { - base: self.base, - overlay: self.overlay, - bounds: self.bounds, - }, - renderer, - ) + Self::build(self.root, bounds, Cache, renderer) } /// Extract the [`Cache`] of the [`UserInterface`], consuming it in the /// process. pub fn into_cache(self) -> Cache { - Cache { - base: self.base, - overlay: self.overlay, - bounds: self.bounds, - } - } - - fn overlay_layer( - cache: Option<Layer>, - bounds: Size, - overlay: &mut overlay::Element<'_, Message, Renderer>, - renderer: &Renderer, - ) -> Layer { - let new_hash = { - let hasher = &mut crate::Hasher::default(); - overlay.hash_layout(hasher); - - hasher.finish() - }; - - let layout = match cache { - Some(Layer { hash, layout }) if new_hash == hash => layout, - _ => overlay.layout(renderer, bounds), - }; - - Layer { - layout, - hash: new_hash, - } + Cache } } -#[derive(Debug, Clone)] -struct Layer { - layout: layout::Node, - hash: u64, -} - /// Reusable data of a specific [`UserInterface`]. #[derive(Debug, Clone)] -pub struct Cache { - base: Layer, - overlay: Option<Layer>, - bounds: Size, -} +pub struct Cache; impl Cache { /// Creates an empty [`Cache`]. @@ -554,14 +449,7 @@ impl Cache { /// You should use this to initialize a [`Cache`] before building your first /// [`UserInterface`]. pub fn new() -> Cache { - Cache { - base: Layer { - layout: layout::Node::new(Size::new(0.0, 0.0)), - hash: 0, - }, - overlay: None, - bounds: Size::ZERO, - } + Cache } } diff --git a/native/src/widget.rs b/native/src/widget.rs index acd2e580..aacdc3d9 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -75,7 +75,7 @@ use crate::layout; use crate::mouse; use crate::overlay; use crate::renderer; -use crate::{Clipboard, Hasher, Layout, Length, Point, Rectangle, Shell}; +use crate::{Clipboard, Layout, Length, Point, Rectangle, Shell}; /// A component that displays information and allows interaction. /// @@ -131,19 +131,6 @@ where viewport: &Rectangle, ); - /// Computes the _layout_ hash of the [`Widget`]. - /// - /// The produced hash is used by the runtime to decide if the [`Layout`] - /// needs to be recomputed between frames. Therefore, to ensure maximum - /// efficiency, the hash should only be affected by the properties of the - /// [`Widget`] that can affect layouting. - /// - /// For example, the [`Text`] widget does not hash its color property, as - /// its value cannot affect the overall [`Layout`] of the user interface. - /// - /// [`Text`]: crate::widget::Text - fn hash_layout(&self, state: &mut Hasher); - /// Processes a runtime [`Event`]. /// /// It receives: diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index b4a3adc3..57fdd7d4 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -8,12 +8,10 @@ use crate::overlay; use crate::renderer; use crate::touch; use crate::{ - Background, Clipboard, Color, Element, Hasher, Layout, Length, Padding, - Point, Rectangle, Shell, Vector, Widget, + Background, Clipboard, Color, Element, Layout, Length, Padding, Point, + Rectangle, Shell, Vector, Widget, }; -use std::hash::Hash; - pub use iced_style::button::{Style, StyleSheet}; /// A generic widget that produces a message when pressed. @@ -333,14 +331,6 @@ where ); } - fn hash_layout(&self, state: &mut Hasher) { - struct Marker; - std::any::TypeId::of::<Marker>().hash(state); - - self.width.hash(state); - self.content.hash_layout(state); - } - fn overlay( &mut self, layout: Layout<'_>, diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index fff65a40..15cbf93a 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -1,6 +1,4 @@ //! Show toggle controls using checkboxes. -use std::hash::Hash; - use crate::alignment; use crate::event::{self, Event}; use crate::layout; @@ -10,8 +8,8 @@ use crate::text; use crate::touch; use crate::widget::{self, Row, Text}; use crate::{ - Alignment, Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, - Shell, Widget, + Alignment, Clipboard, Element, Layout, Length, Point, Rectangle, Shell, + Widget, }; pub use iced_style::checkbox::{Style, StyleSheet}; @@ -262,13 +260,6 @@ where ); } } - - fn hash_layout(&self, state: &mut Hasher) { - struct Marker; - std::any::TypeId::of::<Marker>().hash(state); - - self.label.hash(state); - } } impl<'a, Message, Renderer> From<Checkbox<'a, Message, Renderer>> diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index 66ed5e23..f161d1f2 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -1,14 +1,12 @@ //! Distribute content vertically. -use std::hash::Hash; - use crate::event::{self, Event}; use crate::layout; use crate::mouse; use crate::overlay; use crate::renderer; use crate::{ - Alignment, Clipboard, Element, Hasher, Layout, Length, Padding, Point, - Rectangle, Shell, Widget, + Alignment, Clipboard, Element, Layout, Length, Padding, Point, Rectangle, + Shell, Widget, }; use std::u32; @@ -199,23 +197,6 @@ where } } - fn hash_layout(&self, state: &mut Hasher) { - struct Marker; - std::any::TypeId::of::<Marker>().hash(state); - - self.width.hash(state); - self.height.hash(state); - self.max_width.hash(state); - self.max_height.hash(state); - self.align_items.hash(state); - self.spacing.hash(state); - self.padding.hash(state); - - for child in &self.children { - child.widget.hash_layout(state); - } - } - fn overlay( &mut self, layout: Layout<'_>, diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index 4444732a..ca85a425 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -1,6 +1,4 @@ //! Decorate content and apply alignment. -use std::hash::Hash; - use crate::alignment::{self, Alignment}; use crate::event::{self, Event}; use crate::layout; @@ -8,8 +6,8 @@ use crate::mouse; use crate::overlay; use crate::renderer; use crate::{ - Background, Clipboard, Color, Element, Hasher, Layout, Length, Padding, - Point, Rectangle, Shell, Widget, + Background, Clipboard, Color, Element, Layout, Length, Padding, Point, + Rectangle, Shell, Widget, }; use std::u32; @@ -219,21 +217,6 @@ where ); } - fn hash_layout(&self, state: &mut Hasher) { - struct Marker; - std::any::TypeId::of::<Marker>().hash(state); - - self.padding.hash(state); - self.width.hash(state); - self.height.hash(state); - self.max_width.hash(state); - self.max_height.hash(state); - self.horizontal_alignment.hash(state); - self.vertical_alignment.hash(state); - - self.content.hash_layout(state); - } - fn overlay( &mut self, layout: Layout<'_>, diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs index b8fb662e..de0ffbc0 100644 --- a/native/src/widget/image.rs +++ b/native/src/widget/image.rs @@ -5,7 +5,9 @@ pub use viewer::Viewer; use crate::image; use crate::layout; use crate::renderer; -use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget}; +use crate::{ + ContentFit, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget, +}; use std::hash::Hash; @@ -26,6 +28,7 @@ pub struct Image<Handle> { handle: Handle, width: Length, height: Length, + content_fit: ContentFit, } impl<Handle> Image<Handle> { @@ -35,6 +38,7 @@ impl<Handle> Image<Handle> { handle: handle.into(), width: Length::Shrink, height: Length::Shrink, + content_fit: ContentFit::Contain, } } @@ -49,6 +53,16 @@ impl<Handle> Image<Handle> { self.height = height; self } + + /// Sets the [`ContentFit`] of the [`Image`]. + /// + /// Defaults to [`ContentFit::Contain`] + pub fn content_fit(self, content_fit: ContentFit) -> Self { + Self { + content_fit, + ..self + } + } } impl<Message, Renderer, Handle> Widget<Message, Renderer> for Image<Handle> @@ -69,24 +83,32 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { + // The raw w/h of the underlying image let (width, height) = renderer.dimensions(&self.handle); + let image_size = Size::new(width as f32, height as f32); - let aspect_ratio = width as f32 / height as f32; - - let mut size = limits + // The size to be available to the widget prior to `Shrink`ing + let raw_size = limits .width(self.width) .height(self.height) - .resolve(Size::new(width as f32, height as f32)); - - let viewport_aspect_ratio = size.width / size.height; - - if viewport_aspect_ratio > aspect_ratio { - size.width = width as f32 * size.height / height as f32; - } else { - size.height = height as f32 * size.width / width as f32; - } - - layout::Node::new(size) + .resolve(image_size); + + // The uncropped size of the image when fit to the bounds above + let full_size = self.content_fit.fit(image_size, raw_size); + + // Shrink the widget to fit the resized image, if requested + let final_size = Size { + width: match self.width { + Length::Shrink => f32::min(raw_size.width, full_size.width), + _ => raw_size.width, + }, + height: match self.height { + Length::Shrink => f32::min(raw_size.height, full_size.height), + _ => raw_size.height, + }, + }; + + layout::Node::new(final_size) } fn draw( @@ -97,16 +119,34 @@ where _cursor_position: Point, _viewport: &Rectangle, ) { - renderer.draw(self.handle.clone(), layout.bounds()); - } - - fn hash_layout(&self, state: &mut Hasher) { - struct Marker; - std::any::TypeId::of::<Marker>().hash(state); - - self.handle.hash(state); - self.width.hash(state); - self.height.hash(state); + let (width, height) = renderer.dimensions(&self.handle); + let image_size = Size::new(width as f32, height as f32); + + let bounds = layout.bounds(); + let adjusted_fit = self.content_fit.fit(image_size, bounds.size()); + + let render = |renderer: &mut Renderer| { + let offset = Vector::new( + (bounds.width - adjusted_fit.width).max(0.0) / 2.0, + (bounds.height - adjusted_fit.height).max(0.0) / 2.0, + ); + + let drawing_bounds = Rectangle { + width: adjusted_fit.width, + height: adjusted_fit.height, + ..bounds + }; + + renderer.draw(self.handle.clone(), drawing_bounds + offset) + }; + + if adjusted_fit.width > bounds.width + || adjusted_fit.height > bounds.height + { + renderer.with_layer(bounds, render); + } else { + render(renderer) + } } } diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs index 486d5fa5..840b88e5 100644 --- a/native/src/widget/image/viewer.rs +++ b/native/src/widget/image/viewer.rs @@ -5,8 +5,8 @@ use crate::layout; use crate::mouse; use crate::renderer; use crate::{ - Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Shell, Size, - Vector, Widget, + Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector, + Widget, }; use std::hash::Hash; @@ -335,17 +335,6 @@ where }); }); } - - fn hash_layout(&self, state: &mut Hasher) { - struct Marker; - std::any::TypeId::of::<Marker>().hash(state); - - self.width.hash(state); - self.height.hash(state); - self.padding.hash(state); - - self.handle.hash(state); - } } /// The local state of a [`Viewer`]. diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 5f293fa6..8ad63cf1 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -34,8 +34,8 @@ use crate::overlay; use crate::renderer; use crate::touch; use crate::{ - Clipboard, Color, Element, Hasher, Layout, Length, Point, Rectangle, Shell, - Size, Vector, Widget, + Clipboard, Color, Element, Layout, Length, Point, Rectangle, Shell, Size, + Vector, Widget, }; pub use iced_style::pane_grid::{Line, StyleSheet}; @@ -666,21 +666,6 @@ where } } - fn hash_layout(&self, state: &mut Hasher) { - use std::hash::Hash; - - struct Marker; - std::any::TypeId::of::<Marker>().hash(state); - - self.width.hash(state); - self.height.hash(state); - self.state.hash_layout(state); - - for (_, element) in &self.elements { - element.hash_layout(state); - } - } - fn overlay( &mut self, layout: Layout<'_>, diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs index 6d12aa2d..8b0e8d2a 100644 --- a/native/src/widget/pane_grid/content.rs +++ b/native/src/widget/pane_grid/content.rs @@ -5,9 +5,7 @@ use crate::overlay; use crate::renderer; use crate::widget::container; use crate::widget::pane_grid::TitleBar; -use crate::{ - Clipboard, Element, Hasher, Layout, Point, Rectangle, Shell, Size, -}; +use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size}; /// The content of a [`Pane`]. /// @@ -236,14 +234,6 @@ where .max(title_bar_interaction) } - pub(crate) fn hash_layout(&self, state: &mut Hasher) { - if let Some(title_bar) = &self.title_bar { - title_bar.hash_layout(state); - } - - self.body.hash_layout(state); - } - pub(crate) fn overlay( &mut self, layout: Layout<'_>, diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index bcc724a8..feea0dec 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -1,7 +1,7 @@ use crate::widget::pane_grid::{ Axis, Configuration, Direction, Node, Pane, Split, }; -use crate::{Hasher, Point, Rectangle, Size}; +use crate::{Point, Rectangle, Size}; use std::collections::{BTreeMap, HashMap}; @@ -292,10 +292,4 @@ impl Internal { pub fn idle(&mut self) { self.action = Action::Idle; } - - pub fn hash_layout(&self, hasher: &mut Hasher) { - use std::hash::Hash; - - self.layout.hash(hasher); - } } diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs index ea9ebfc4..d56972ec 100644 --- a/native/src/widget/pane_grid/title_bar.rs +++ b/native/src/widget/pane_grid/title_bar.rs @@ -5,7 +5,7 @@ use crate::overlay; use crate::renderer; use crate::widget::container; use crate::{ - Clipboard, Element, Hasher, Layout, Padding, Point, Rectangle, Shell, Size, + Clipboard, Element, Layout, Padding, Point, Rectangle, Shell, Size, }; /// The title bar of a [`Pane`]. @@ -157,17 +157,6 @@ where } } - pub(crate) fn hash_layout(&self, hasher: &mut Hasher) { - use std::hash::Hash; - - self.content.hash_layout(hasher); - self.padding.hash(hasher); - - if let Some(controls) = &self.controls { - controls.hash_layout(hasher); - } - } - pub(crate) fn layout( &self, renderer: &Renderer, diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index a200fb13..3be6c20c 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -10,8 +10,8 @@ use crate::renderer; use crate::text::{self, Text}; use crate::touch; use crate::{ - Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle, - Shell, Size, Widget, + Clipboard, Element, Layout, Length, Padding, Point, Rectangle, Shell, Size, + Widget, }; use std::borrow::Cow; @@ -220,24 +220,6 @@ where layout::Node::new(size) } - fn hash_layout(&self, state: &mut Hasher) { - use std::hash::Hash as _; - - match self.width { - Length::Shrink => { - self.placeholder.hash(state); - - self.options - .iter() - .map(ToString::to_string) - .for_each(|label| label.hash(state)); - } - _ => { - self.width.hash(state); - } - } - } - fn on_event( &mut self, event: Event, diff --git a/native/src/widget/progress_bar.rs b/native/src/widget/progress_bar.rs index 69eb8c09..c26c38fa 100644 --- a/native/src/widget/progress_bar.rs +++ b/native/src/widget/progress_bar.rs @@ -1,11 +1,9 @@ //! Provide progress feedback to your users. use crate::layout; use crate::renderer; -use crate::{ - Color, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget, -}; +use crate::{Color, Element, Layout, Length, Point, Rectangle, Size, Widget}; -use std::{hash::Hash, ops::RangeInclusive}; +use std::ops::RangeInclusive; pub use iced_style::progress_bar::{Style, StyleSheet}; @@ -141,14 +139,6 @@ where ); } } - - fn hash_layout(&self, state: &mut Hasher) { - struct Marker; - std::any::TypeId::of::<Marker>().hash(state); - - self.width.hash(state); - self.height.hash(state); - } } impl<'a, Message, Renderer> From<ProgressBar<'a>> diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index c4992764..fed2925b 100644 --- a/native/src/widget/radio.rs +++ b/native/src/widget/radio.rs @@ -1,6 +1,4 @@ //! Create choices using radio buttons. -use std::hash::Hash; - use crate::alignment; use crate::event::{self, Event}; use crate::layout; @@ -10,8 +8,8 @@ use crate::text; use crate::touch; use crate::widget::{self, Row, Text}; use crate::{ - Alignment, Clipboard, Color, Element, Hasher, Layout, Length, Point, - Rectangle, Shell, Widget, + Alignment, Clipboard, Color, Element, Layout, Length, Point, Rectangle, + Shell, Widget, }; pub use iced_style::radio::{Style, StyleSheet}; @@ -280,13 +278,6 @@ where ); } } - - fn hash_layout(&self, state: &mut Hasher) { - struct Marker; - std::any::TypeId::of::<Marker>().hash(state); - - self.label.hash(state); - } } impl<'a, Message, Renderer> From<Radio<'a, Message, Renderer>> diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index c423d31f..e34befb2 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -5,11 +5,10 @@ use crate::mouse; use crate::overlay; use crate::renderer; use crate::{ - Alignment, Clipboard, Element, Hasher, Layout, Length, Padding, Point, - Rectangle, Shell, Widget, + Alignment, Clipboard, Element, Layout, Length, Padding, Point, Rectangle, + Shell, Widget, }; -use std::hash::Hash; use std::u32; /// A container that distributes its contents horizontally. @@ -198,23 +197,6 @@ where } } - fn hash_layout(&self, state: &mut Hasher) { - struct Marker; - std::any::TypeId::of::<Marker>().hash(state); - - self.width.hash(state); - self.height.hash(state); - self.max_width.hash(state); - self.max_height.hash(state); - self.align_items.hash(state); - self.spacing.hash(state); - self.padding.hash(state); - - for child in &self.children { - child.widget.hash_layout(state); - } - } - fn overlay( &mut self, layout: Layout<'_>, diff --git a/native/src/widget/rule.rs b/native/src/widget/rule.rs index 7c8c5dbc..b0cc3768 100644 --- a/native/src/widget/rule.rs +++ b/native/src/widget/rule.rs @@ -1,11 +1,7 @@ //! Display a horizontal or vertical rule for dividing content. use crate::layout; use crate::renderer; -use crate::{ - Color, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget, -}; - -use std::hash::Hash; +use crate::{Color, Element, Layout, Length, Point, Rectangle, Size, Widget}; pub use iced_style::rule::{FillMode, Style, StyleSheet}; @@ -122,14 +118,6 @@ where style.color, ); } - - fn hash_layout(&self, state: &mut Hasher) { - struct Marker; - std::any::TypeId::of::<Marker>().hash(state); - - self.width.hash(state); - self.height.hash(state); - } } impl<'a, Message, Renderer> From<Rule<'a>> for Element<'a, Message, Renderer> diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 4b005c6b..ce734ad8 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -7,11 +7,11 @@ use crate::renderer; use crate::touch; use crate::widget::Column; use crate::{ - Alignment, Background, Clipboard, Color, Element, Hasher, Layout, Length, - Padding, Point, Rectangle, Shell, Size, Vector, Widget, + Alignment, Background, Clipboard, Color, Element, Layout, Length, Padding, + Point, Rectangle, Shell, Size, Vector, Widget, }; -use std::{f32, hash::Hash, u32}; +use std::{f32, u32}; pub use iced_style::scrollable::StyleSheet; @@ -570,16 +570,6 @@ where } } - fn hash_layout(&self, state: &mut Hasher) { - struct Marker; - std::any::TypeId::of::<Marker>().hash(state); - - self.height.hash(state); - self.max_height.hash(state); - - self.content.hash_layout(state) - } - fn overlay( &mut self, layout: Layout<'_>, diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index 917bfc14..289f75f5 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -7,11 +7,10 @@ use crate::mouse; use crate::renderer; use crate::touch; use crate::{ - Background, Clipboard, Color, Element, Hasher, Layout, Length, Point, - Rectangle, Shell, Size, Widget, + Background, Clipboard, Color, Element, Layout, Length, Point, Rectangle, + Shell, Size, Widget, }; -use std::hash::Hash; use std::ops::RangeInclusive; pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet}; @@ -374,13 +373,6 @@ where mouse::Interaction::default() } } - - fn hash_layout(&self, state: &mut Hasher) { - struct Marker; - std::any::TypeId::of::<Marker>().hash(state); - - self.width.hash(state); - } } impl<'a, T, Message, Renderer> From<Slider<'a, T, Message>> diff --git a/native/src/widget/space.rs b/native/src/widget/space.rs index 3373f3b7..4135d1b8 100644 --- a/native/src/widget/space.rs +++ b/native/src/widget/space.rs @@ -1,9 +1,7 @@ //! Distribute content vertically. use crate::layout; use crate::renderer; -use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget}; - -use std::hash::Hash; +use crate::{Element, Layout, Length, Point, Rectangle, Size, Widget}; /// An amount of empty space. /// @@ -68,13 +66,6 @@ where _viewport: &Rectangle, ) { } - - fn hash_layout(&self, state: &mut Hasher) { - std::any::TypeId::of::<Space>().hash(state); - - self.width.hash(state); - self.height.hash(state); - } } impl<'a, Message, Renderer> From<Space> for Element<'a, Message, Renderer> diff --git a/native/src/widget/svg.rs b/native/src/widget/svg.rs index f212dfcb..008ab356 100644 --- a/native/src/widget/svg.rs +++ b/native/src/widget/svg.rs @@ -2,9 +2,10 @@ use crate::layout; use crate::renderer; use crate::svg::{self, Handle}; -use crate::{Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget}; +use crate::{ + ContentFit, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget, +}; -use std::hash::Hash; use std::path::PathBuf; /// A vector graphics image. @@ -18,6 +19,7 @@ pub struct Svg { handle: Handle, width: Length, height: Length, + content_fit: ContentFit, } impl Svg { @@ -27,6 +29,7 @@ impl Svg { handle: handle.into(), width: Length::Fill, height: Length::Shrink, + content_fit: ContentFit::Contain, } } @@ -47,6 +50,16 @@ impl Svg { self.height = height; self } + + /// Sets the [`ContentFit`] of the [`Svg`]. + /// + /// Defaults to [`ContentFit::Contain`] + pub fn content_fit(self, content_fit: ContentFit) -> Self { + Self { + content_fit, + ..self + } + } } impl<Message, Renderer> Widget<Message, Renderer> for Svg @@ -66,24 +79,32 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { + // The raw w/h of the underlying image let (width, height) = renderer.dimensions(&self.handle); + let image_size = Size::new(width as f32, height as f32); - let aspect_ratio = width as f32 / height as f32; - - let mut size = limits + // The size to be available to the widget prior to `Shrink`ing + let raw_size = limits .width(self.width) .height(self.height) - .resolve(Size::new(width as f32, height as f32)); - - let viewport_aspect_ratio = size.width / size.height; - - if viewport_aspect_ratio > aspect_ratio { - size.width = width as f32 * size.height / height as f32; - } else { - size.height = height as f32 * size.width / width as f32; - } - - layout::Node::new(size) + .resolve(image_size); + + // The uncropped size of the image when fit to the bounds above + let full_size = self.content_fit.fit(image_size, raw_size); + + // Shrink the widget to fit the resized image, if requested + let final_size = Size { + width: match self.width { + Length::Shrink => f32::min(raw_size.width, full_size.width), + _ => raw_size.width, + }, + height: match self.height { + Length::Shrink => f32::min(raw_size.height, full_size.height), + _ => raw_size.height, + }, + }; + + layout::Node::new(final_size) } fn draw( @@ -94,15 +115,34 @@ where _cursor_position: Point, _viewport: &Rectangle, ) { - renderer.draw(self.handle.clone(), layout.bounds()) - } - - fn hash_layout(&self, state: &mut Hasher) { - std::any::TypeId::of::<Svg>().hash(state); - - self.handle.hash(state); - self.width.hash(state); - self.height.hash(state); + let (width, height) = renderer.dimensions(&self.handle); + let image_size = Size::new(width as f32, height as f32); + + let bounds = layout.bounds(); + let adjusted_fit = self.content_fit.fit(image_size, bounds.size()); + + let render = |renderer: &mut Renderer| { + let offset = Vector::new( + (bounds.width - adjusted_fit.width).max(0.0) / 2.0, + (bounds.height - adjusted_fit.height).max(0.0) / 2.0, + ); + + let drawing_bounds = Rectangle { + width: adjusted_fit.width, + height: adjusted_fit.height, + ..bounds + }; + + renderer.draw(self.handle.clone(), drawing_bounds + offset) + }; + + if adjusted_fit.width > bounds.width + || adjusted_fit.height > bounds.height + { + renderer.with_layer(bounds, render); + } else { + render(renderer) + } } } diff --git a/native/src/widget/text.rs b/native/src/widget/text.rs index f0b29d15..6f00c9c8 100644 --- a/native/src/widget/text.rs +++ b/native/src/widget/text.rs @@ -3,11 +3,7 @@ use crate::alignment; use crate::layout; use crate::renderer; use crate::text; -use crate::{ - Color, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget, -}; - -use std::hash::Hash; +use crate::{Color, Element, Layout, Length, Point, Rectangle, Size, Widget}; /// A paragraph of text. /// @@ -151,16 +147,6 @@ where self.vertical_alignment, ); } - - fn hash_layout(&self, state: &mut Hasher) { - struct Marker; - std::any::TypeId::of::<Marker>().hash(state); - - self.content.hash(state); - self.size.hash(state); - self.width.hash(state); - self.height.hash(state); - } } /// Draws text using the same logic as the [`Text`] widget. diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 03adeb12..e30e2343 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -20,8 +20,8 @@ use crate::renderer; use crate::text::{self, Text}; use crate::touch; use crate::{ - Clipboard, Color, Element, Hasher, Layout, Length, Padding, Point, - Rectangle, Shell, Size, Vector, Widget, + Clipboard, Color, Element, Layout, Length, Padding, Point, Rectangle, + Shell, Size, Vector, Widget, }; use std::u32; @@ -783,17 +783,6 @@ where ) { self.draw(renderer, layout, cursor_position, None) } - - fn hash_layout(&self, state: &mut Hasher) { - use std::{any::TypeId, hash::Hash}; - struct Marker; - TypeId::of::<Marker>().hash(state); - - self.width.hash(state); - self.max_width.hash(state); - self.padding.hash(state); - self.size.hash(state); - } } impl<'a, Message, Renderer> From<TextInput<'a, Message, Renderer>> diff --git a/native/src/widget/toggler.rs b/native/src/widget/toggler.rs index 002e0a4f..48237edb 100644 --- a/native/src/widget/toggler.rs +++ b/native/src/widget/toggler.rs @@ -1,5 +1,4 @@ //! Show toggle controls using togglers. -use std::hash::Hash; use crate::alignment; use crate::event; @@ -9,8 +8,8 @@ use crate::renderer; use crate::text; use crate::widget::{Row, Text}; use crate::{ - Alignment, Clipboard, Element, Event, Hasher, Layout, Length, Point, - Rectangle, Shell, Widget, + Alignment, Clipboard, Element, Event, Layout, Length, Point, Rectangle, + Shell, Widget, }; pub use iced_style::toggler::{Style, StyleSheet}; @@ -295,13 +294,6 @@ where style.foreground, ); } - - fn hash_layout(&self, state: &mut Hasher) { - struct Marker; - std::any::TypeId::of::<Marker>().hash(state); - - self.label.hash(state) - } } impl<'a, Message, Renderer> From<Toggler<'a, Message, Renderer>> diff --git a/native/src/widget/tooltip.rs b/native/src/widget/tooltip.rs index f6630864..7989c768 100644 --- a/native/src/widget/tooltip.rs +++ b/native/src/widget/tooltip.rs @@ -1,8 +1,4 @@ //! Display a widget over another. -use std::hash::Hash; - -use iced_core::Rectangle; - use crate::event; use crate::layout; use crate::mouse; @@ -11,8 +7,8 @@ use crate::text; use crate::widget::container; use crate::widget::text::Text; use crate::{ - Clipboard, Element, Event, Hasher, Layout, Length, Padding, Point, Shell, - Size, Vector, Widget, + Clipboard, Element, Event, Layout, Length, Padding, Point, Rectangle, + Shell, Size, Vector, Widget, }; /// An element to display a widget over another. @@ -268,13 +264,6 @@ where }); } } - - fn hash_layout(&self, state: &mut Hasher) { - struct Marker; - std::any::TypeId::of::<Marker>().hash(state); - - self.content.hash_layout(state); - } } impl<'a, Message, Renderer> From<Tooltip<'a, Message, Renderer>> diff --git a/rustfmt.toml b/rustfmt.toml index fa50ab92..501828b4 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,2 +1,2 @@ max_width=80 -edition="2018" +edition="2021" diff --git a/src/application.rs b/src/application.rs index b722c8a3..14a16d61 100644 --- a/src/application.rs +++ b/src/application.rs @@ -198,39 +198,28 @@ pub trait Application: Sized { where Self: 'static, { - #[cfg(not(target_arch = "wasm32"))] - { - let renderer_settings = crate::renderer::Settings { - default_font: settings.default_font, - default_text_size: settings.default_text_size, - text_multithreading: settings.text_multithreading, - antialiasing: if settings.antialiasing { - Some(crate::renderer::settings::Antialiasing::MSAAx4) - } else { - None - }, - ..crate::renderer::Settings::from_env() - }; - - Ok(crate::runtime::application::run::< - Instance<Self>, - Self::Executor, - crate::renderer::window::Compositor, - >(settings.into(), renderer_settings)?) - } - - #[cfg(target_arch = "wasm32")] - { - <Instance<Self> as iced_web::Application>::run(settings.flags); - - Ok(()) - } + let renderer_settings = crate::renderer::Settings { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + text_multithreading: settings.text_multithreading, + antialiasing: if settings.antialiasing { + Some(crate::renderer::settings::Antialiasing::MSAAx4) + } else { + None + }, + ..crate::renderer::Settings::from_env() + }; + + Ok(crate::runtime::application::run::< + Instance<Self>, + Self::Executor, + crate::renderer::window::Compositor, + >(settings.into(), renderer_settings)?) } } struct Instance<A: Application>(A); -#[cfg(not(target_arch = "wasm32"))] impl<A> iced_winit::Program for Instance<A> where A: Application, @@ -247,7 +236,6 @@ where } } -#[cfg(not(target_arch = "wasm32"))] impl<A> crate::runtime::Application for Instance<A> where A: Application, @@ -288,35 +276,3 @@ where self.0.should_exit() } } - -#[cfg(target_arch = "wasm32")] -impl<A> iced_web::Application for Instance<A> -where - A: Application, -{ - type Executor = A::Executor; - type Message = A::Message; - type Flags = A::Flags; - - fn new(flags: Self::Flags) -> (Self, Command<A::Message>) { - let (app, command) = A::new(flags); - - (Instance(app), command) - } - - fn title(&self) -> String { - self.0.title() - } - - fn update(&mut self, message: Self::Message) -> Command<Self::Message> { - self.0.update(message) - } - - fn subscription(&self) -> Subscription<Self::Message> { - self.0.subscription() - } - - fn view(&mut self) -> Element<'_, Self::Message> { - self.0.view() - } -} diff --git a/src/element.rs b/src/element.rs index 6f47c701..8bad18c1 100644 --- a/src/element.rs +++ b/src/element.rs @@ -1,9 +1,5 @@ /// A generic widget. /// /// This is an alias of an `iced_native` element with a default `Renderer`. -#[cfg(not(target_arch = "wasm32"))] pub type Element<'a, Message> = crate::runtime::Element<'a, Message, crate::renderer::Renderer>; - -#[cfg(target_arch = "wasm32")] -pub use iced_web::Element; diff --git a/src/error.rs b/src/error.rs index c8fa6636..17479c60 100644 --- a/src/error.rs +++ b/src/error.rs @@ -16,7 +16,6 @@ pub enum Error { GraphicsAdapterNotFound, } -#[cfg(not(target_arch = "wasm32"))] impl From<iced_winit::Error> for Error { fn from(error: iced_winit::Error) -> Error { match error { diff --git a/src/executor.rs b/src/executor.rs index c7166c68..36ae274e 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -1,86 +1,14 @@ //! Choose your preferred executor to power your application. pub use crate::runtime::Executor; -pub use platform::Default; - -#[cfg(not(target_arch = "wasm32"))] -mod platform { - use iced_futures::{executor, futures}; - - #[cfg(feature = "tokio")] - type Executor = executor::Tokio; - - #[cfg(all(feature = "async-std", not(feature = "tokio"),))] - type Executor = executor::AsyncStd; - - #[cfg(all( - feature = "smol", - not(any(feature = "tokio", feature = "async-std")), - ))] - type Executor = executor::Smol; - - #[cfg(not(any( - feature = "tokio", - feature = "async-std", - feature = "smol", - )))] - type Executor = executor::ThreadPool; - - /// A default cross-platform executor. - /// - /// - On native platforms, it will use: - /// - `iced_futures::executor::Tokio` when the `tokio` feature is enabled. - /// - `iced_futures::executor::AsyncStd` when the `async-std` feature is - /// enabled. - /// - `iced_futures::executor::ThreadPool` otherwise. - /// - On the Web, it will use `iced_futures::executor::WasmBindgen`. - #[derive(Debug)] - pub struct Default(Executor); - - impl super::Executor for Default { - fn new() -> Result<Self, futures::io::Error> { - Ok(Default(Executor::new()?)) - } - - fn spawn( - &self, - future: impl futures::Future<Output = ()> + Send + 'static, - ) { - let _ = self.0.spawn(future); - } - - fn enter<R>(&self, f: impl FnOnce() -> R) -> R { - super::Executor::enter(&self.0, f) - } - } -} - -#[cfg(target_arch = "wasm32")] -mod platform { - use iced_futures::{executor::WasmBindgen, futures, Executor}; - - /// A default cross-platform executor. - /// - /// - On native platforms, it will use: - /// - `iced_futures::executor::Tokio` when the `tokio` feature is enabled. - /// - `iced_futures::executor::AsyncStd` when the `async-std` feature is - /// enabled. - /// - `iced_futures::executor::ThreadPool` otherwise. - /// - On the Web, it will use `iced_futures::executor::WasmBindgen`. - #[derive(Debug)] - pub struct Default(WasmBindgen); - - impl Executor for Default { - fn new() -> Result<Self, futures::io::Error> { - Ok(Default(WasmBindgen::new()?)) - } - - fn spawn(&self, future: impl futures::Future<Output = ()> + 'static) { - self.0.spawn(future); - } - - fn enter<R>(&self, f: impl FnOnce() -> R) -> R { - self.0.enter(f) - } - } -} +/// A default cross-platform executor. +/// +/// - On native platforms, it will use: +/// - `iced_futures::backend::native::tokio` when the `tokio` feature is enabled. +/// - `iced_futures::backend::native::async-std` when the `async-std` feature is +/// enabled. +/// - `iced_futures::backend::native::smol` when the `smol` feature is enabled. +/// - `iced_futures::backend::native::thread_pool` otherwise. +/// +/// - On Wasm, it will use `iced_futures::backend::wasm::wasm_bindgen`. +pub type Default = iced_futures::backend::default::Executor; @@ -191,46 +191,22 @@ pub mod executor; pub mod keyboard; pub mod mouse; pub mod settings; +pub mod time; pub mod widget; pub mod window; -#[cfg(all( - any(feature = "tokio", feature = "async-std", feature = "smol"), - not(target_arch = "wasm32") -))] -#[cfg_attr( - docsrs, - doc(cfg(any( - feature = "tokio", - feature = "async-std" - feature = "smol" - ))) -)] -pub mod time; - -#[cfg(all( - not(target_arch = "wasm32"), - not(feature = "glow"), - feature = "wgpu" -))] +#[cfg(all(not(feature = "glow"), feature = "wgpu"))] use iced_winit as runtime; -#[cfg(all(not(target_arch = "wasm32"), feature = "glow"))] +#[cfg(feature = "glow")] use iced_glutin as runtime; -#[cfg(all( - not(target_arch = "wasm32"), - not(feature = "glow"), - feature = "wgpu" -))] +#[cfg(all(not(feature = "glow"), feature = "wgpu"))] use iced_wgpu as renderer; -#[cfg(all(not(target_arch = "wasm32"), feature = "glow"))] +#[cfg(feature = "glow")] use iced_glow as renderer; -#[cfg(target_arch = "wasm32")] -use iced_web as runtime; - #[doc(no_inline)] pub use widget::*; @@ -245,6 +221,6 @@ pub use settings::Settings; pub use runtime::alignment; pub use runtime::futures; pub use runtime::{ - Alignment, Background, Color, Command, Font, Length, Point, Rectangle, - Size, Subscription, Vector, + Alignment, Background, Color, Command, ContentFit, Font, Length, Point, + Rectangle, Size, Subscription, Vector, }; diff --git a/src/sandbox.rs b/src/sandbox.rs index aabfb9c7..2306c650 100644 --- a/src/sandbox.rs +++ b/src/sandbox.rs @@ -156,7 +156,7 @@ impl<T> Application for T where T: Sandbox, { - type Executor = crate::runtime::executor::Null; + type Executor = iced_futures::backend::null::Executor; type Flags = (); type Message = T::Message; diff --git a/src/settings.rs b/src/settings.rs index f7940a0b..d31448fb 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -55,6 +55,15 @@ pub struct Settings<Flags> { /// /// [`Application`]: crate::Application pub exit_on_close_request: bool, + + /// Whether the [`Application`] should try to build the context + /// using OpenGL ES first then OpenGL. + /// + /// By default, it is disabled. + /// **Note:** Only works for the `glow` backend. + /// + /// [`Application`]: crate::Application + pub try_opengles_first: bool, } impl<Flags> Settings<Flags> { @@ -73,6 +82,7 @@ impl<Flags> Settings<Flags> { text_multithreading: default_settings.text_multithreading, antialiasing: default_settings.antialiasing, exit_on_close_request: default_settings.exit_on_close_request, + try_opengles_first: default_settings.try_opengles_first, } } } @@ -91,11 +101,11 @@ where text_multithreading: false, antialiasing: false, exit_on_close_request: true, + try_opengles_first: false, } } } -#[cfg(not(target_arch = "wasm32"))] impl<Flags> From<Settings<Flags>> for iced_winit::Settings<Flags> { fn from(settings: Settings<Flags>) -> iced_winit::Settings<Flags> { iced_winit::Settings { @@ -103,6 +113,7 @@ impl<Flags> From<Settings<Flags>> for iced_winit::Settings<Flags> { window: settings.window.into(), flags: settings.flags, exit_on_close_request: settings.exit_on_close_request, + try_opengles_first: settings.try_opengles_first, } } } diff --git a/src/time.rs b/src/time.rs index b8432895..37d454ed 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,12 +1,4 @@ //! Listen and react to time. -use crate::Subscription; +pub use iced_core::time::{Duration, Instant}; -/// Returns a [`Subscription`] that produces messages at a set interval. -/// -/// The first message is produced after a `duration`, and then continues to -/// produce more messages every `duration` after that. -pub fn every( - duration: std::time::Duration, -) -> Subscription<std::time::Instant> { - iced_futures::time::every(duration) -} +pub use iced_futures::backend::default::time::*; diff --git a/src/widget.rs b/src/widget.rs index 0f0b0325..c619bcfa 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -13,63 +13,53 @@ //! //! These widgets have their own module with a `State` type. For instance, a //! [`TextInput`] has some [`text_input::State`]. -#[cfg(not(target_arch = "wasm32"))] -mod platform { - pub use crate::renderer::widget::{ - button, checkbox, container, pane_grid, pick_list, progress_bar, radio, - rule, scrollable, slider, text_input, toggler, tooltip, Column, Row, - Space, Text, - }; +pub use crate::renderer::widget::{ + button, checkbox, container, pane_grid, pick_list, progress_bar, radio, + rule, scrollable, slider, text_input, toggler, tooltip, Column, Row, Space, + Text, +}; - #[cfg(any(feature = "canvas", feature = "glow_canvas"))] - #[cfg_attr( - docsrs, - doc(cfg(any(feature = "canvas", feature = "glow_canvas"))) - )] - pub use crate::renderer::widget::canvas; +#[cfg(any(feature = "canvas", feature = "glow_canvas"))] +#[cfg_attr( + docsrs, + doc(cfg(any(feature = "canvas", feature = "glow_canvas"))) +)] +pub use crate::renderer::widget::canvas; - #[cfg(any(feature = "qr_code", feature = "glow_qr_code"))] - #[cfg_attr( - docsrs, - doc(cfg(any(feature = "qr_code", feature = "glow_qr_code"))) - )] - pub use crate::renderer::widget::qr_code; +#[cfg(any(feature = "qr_code", feature = "glow_qr_code"))] +#[cfg_attr( + docsrs, + doc(cfg(any(feature = "qr_code", feature = "glow_qr_code"))) +)] +pub use crate::renderer::widget::qr_code; - #[cfg_attr(docsrs, doc(cfg(feature = "image")))] - pub mod image { - //! Display images in your user interface. - pub use crate::runtime::image::Handle; - pub use crate::runtime::widget::image::viewer; - pub use crate::runtime::widget::image::{Image, Viewer}; - } - - #[cfg_attr(docsrs, doc(cfg(feature = "svg")))] - pub mod svg { - //! Display vector graphics in your user interface. - pub use crate::runtime::svg::Handle; - pub use crate::runtime::widget::svg::Svg; - } - - #[doc(no_inline)] - pub use { - button::Button, checkbox::Checkbox, container::Container, image::Image, - pane_grid::PaneGrid, pick_list::PickList, progress_bar::ProgressBar, - radio::Radio, rule::Rule, scrollable::Scrollable, slider::Slider, - svg::Svg, text_input::TextInput, toggler::Toggler, tooltip::Tooltip, - }; - - #[cfg(any(feature = "canvas", feature = "glow_canvas"))] - #[doc(no_inline)] - pub use canvas::Canvas; - - #[cfg(any(feature = "qr_code", feature = "glow_qr_code"))] - #[doc(no_inline)] - pub use qr_code::QRCode; +#[cfg_attr(docsrs, doc(cfg(feature = "image")))] +pub mod image { + //! Display images in your user interface. + pub use crate::runtime::image::Handle; + pub use crate::runtime::widget::image::viewer; + pub use crate::runtime::widget::image::{Image, Viewer}; } -#[cfg(target_arch = "wasm32")] -mod platform { - pub use iced_web::widget::*; +#[cfg_attr(docsrs, doc(cfg(feature = "svg")))] +pub mod svg { + //! Display vector graphics in your user interface. + pub use crate::runtime::svg::Handle; + pub use crate::runtime::widget::svg::Svg; } -pub use platform::*; +#[doc(no_inline)] +pub use { + button::Button, checkbox::Checkbox, container::Container, image::Image, + pane_grid::PaneGrid, pick_list::PickList, progress_bar::ProgressBar, + radio::Radio, rule::Rule, scrollable::Scrollable, slider::Slider, svg::Svg, + text_input::TextInput, toggler::Toggler, tooltip::Tooltip, +}; + +#[cfg(any(feature = "canvas", feature = "glow_canvas"))] +#[doc(no_inline)] +pub use canvas::Canvas; + +#[cfg(any(feature = "qr_code", feature = "glow_qr_code"))] +#[doc(no_inline)] +pub use qr_code::QRCode; diff --git a/src/window/icon.rs b/src/window/icon.rs index 287538b1..aacadfca 100644 --- a/src/window/icon.rs +++ b/src/window/icon.rs @@ -3,18 +3,11 @@ use std::fmt; use std::io; /// The icon of a window. -#[cfg(not(target_arch = "wasm32"))] #[derive(Debug, Clone)] pub struct Icon(iced_winit::winit::window::Icon); -/// The icon of a window. -#[cfg(target_arch = "wasm32")] -#[derive(Debug, Clone)] -pub struct Icon; - impl Icon { /// Creates an icon from 32bpp RGBA data. - #[cfg(not(target_arch = "wasm32"))] pub fn from_rgba( rgba: Vec<u8>, width: u32, @@ -25,16 +18,6 @@ impl Icon { Ok(Icon(raw)) } - - /// Creates an icon from 32bpp RGBA data. - #[cfg(target_arch = "wasm32")] - pub fn from_rgba( - _rgba: Vec<u8>, - _width: u32, - _height: u32, - ) -> Result<Self, Error> { - Ok(Icon) - } } /// An error produced when using `Icon::from_rgba` with invalid arguments. @@ -62,7 +45,6 @@ pub enum Error { OsError(io::Error), } -#[cfg(not(target_arch = "wasm32"))] impl From<iced_winit::winit::window::BadIcon> for Error { fn from(error: iced_winit::winit::window::BadIcon) -> Self { use iced_winit::winit::window::BadIcon; @@ -86,7 +68,6 @@ impl From<iced_winit::winit::window::BadIcon> for Error { } } -#[cfg(not(target_arch = "wasm32"))] impl From<Icon> for iced_winit::winit::window::Icon { fn from(icon: Icon) -> Self { icon.0 diff --git a/src/window/position.rs b/src/window/position.rs index 8535ef6a..6b9fac41 100644 --- a/src/window/position.rs +++ b/src/window/position.rs @@ -21,7 +21,6 @@ impl Default for Position { } } -#[cfg(not(target_arch = "wasm32"))] impl From<Position> for iced_winit::Position { fn from(position: Position) -> Self { match position { diff --git a/src/window/settings.rs b/src/window/settings.rs index ec6c3071..8e32f4fb 100644 --- a/src/window/settings.rs +++ b/src/window/settings.rs @@ -47,7 +47,6 @@ impl Default for Settings { } } -#[cfg(not(target_arch = "wasm32"))] impl From<Settings> for iced_winit::settings::Window { fn from(settings: Settings) -> Self { Self { diff --git a/style/Cargo.toml b/style/Cargo.toml index 536368ac..047c905d 100644 --- a/style/Cargo.toml +++ b/style/Cargo.toml @@ -2,7 +2,7 @@ name = "iced_style" version = "0.3.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" description = "The default set of styles of Iced" license = "MIT" repository = "https://github.com/iced-rs/iced" diff --git a/web/Cargo.toml b/web/Cargo.toml deleted file mode 100644 index 01a19e7c..00000000 --- a/web/Cargo.toml +++ /dev/null @@ -1,48 +0,0 @@ -[package] -name = "iced_web" -version = "0.4.0" -authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" -description = "A web backend for Iced" -license = "MIT" -repository = "https://github.com/iced-rs/iced" -documentation = "https://docs.rs/iced_web" -readme = "README.md" -keywords = ["gui", "ui", "web", "interface", "widgets"] -categories = ["web-programming"] - -[badges] -maintenance = { status = "actively-developed" } - -[dependencies] -dodrio = "0.2" -wasm-bindgen = "0.2" -wasm-bindgen-futures = "0.4" -url = "2.0" -num-traits = "0.2" -base64 = "0.13" - -[dependencies.iced_core] -version = "0.4" -path = "../core" - -[dependencies.iced_futures] -version = "0.3" -path = "../futures" - -[dependencies.iced_style] -version = "0.3" -path = "../style" - -[dependencies.web-sys] -version = "0.3.27" -features = [ - "console", - "Document", - "HtmlElement", - "HtmlInputElement", - "Event", - "EventTarget", - "InputEvent", - "KeyboardEvent", -] diff --git a/web/README.md b/web/README.md deleted file mode 100644 index b797385e..00000000 --- a/web/README.md +++ /dev/null @@ -1,68 +0,0 @@ -# `iced_web` -[][documentation] -[](https://crates.io/crates/iced_web) -[](https://github.com/iced-rs/iced/blob/master/LICENSE) -[](https://iced.zulipchat.com) - -`iced_web` takes [`iced_core`] and builds a WebAssembly runtime on top. It achieves this by introducing a `Widget` trait that can be used to produce VDOM nodes. - -The crate is currently a __very experimental__, simple abstraction layer over [`dodrio`]. - -[documentation]: https://docs.rs/iced_web -[`iced_core`]: ../core -[`dodrio`]: https://github.com/fitzgen/dodrio - -## Installation -Add `iced_web` as a dependency in your `Cargo.toml`: - -```toml -iced_web = "0.4" -``` - -__Iced moves fast and the `master` branch can contain breaking changes!__ If -you want to learn about a specific release, check out [the release list]. - -[the release list]: https://github.com/iced-rs/iced/releases - -## Usage -The current build process is a bit involved, as [`wasm-pack`] does not currently [support building binary crates](https://github.com/rustwasm/wasm-pack/issues/734). - -Therefore, we instead build using the `wasm32-unknown-unknown` target and use the [`wasm-bindgen`] CLI to generate appropriate bindings. - -For instance, let's say we want to build the [`tour` example]: - -``` -cd examples -cargo build --package tour --target wasm32-unknown-unknown -wasm-bindgen ../target/wasm32-unknown-unknown/debug/tour.wasm --out-dir tour --web -``` - -*__Note:__ Keep in mind that Iced is still in early exploration stages and most of the work needs to happen on the native side of the ecosystem. At this stage, it is important to be able to batch work without having to constantly jump back and forth. Because of this, there is currently no requirement for the `master` branch to contain a cross-platform API at all times. If you hit an issue when building an example and want to help, it may be a good way to [start contributing]!* - -[start contributing]: ../CONTRIBUTING.md - -Once the example is compiled, we need to create an `.html` file to load our application: - -```html -<!DOCTYPE html> -<html> - <head> - <meta http-equiv="Content-type" content="text/html; charset=utf-8"/> - <meta name="viewport" content="width=device-width, initial-scale=1"> - <title>Tour - Iced</title> - </head> - <body> - <script type="module"> - import init from "./tour/tour.js"; - - init('./tour/tour_bg.wasm'); - </script> - </body> -</html> -``` - -Finally, we serve it using an HTTP server and access it with our browser. - -[`wasm-pack`]: https://github.com/rustwasm/wasm-pack -[`wasm-bindgen`]: https://github.com/rustwasm/wasm-bindgen -[`tour` example]: ../examples/README.md#tour diff --git a/web/src/bus.rs b/web/src/bus.rs deleted file mode 100644 index 5ce8e810..00000000 --- a/web/src/bus.rs +++ /dev/null @@ -1,53 +0,0 @@ -use iced_futures::futures::channel::mpsc; -use std::rc::Rc; - -/// A publisher of messages. -/// -/// It can be used to route messages back to the [`Application`]. -/// -/// [`Application`]: crate::Application -#[allow(missing_debug_implementations)] -pub struct Bus<Message> { - publish: Rc<Box<dyn Fn(Message) -> ()>>, -} - -impl<Message> Clone for Bus<Message> { - fn clone(&self) -> Self { - Bus { - publish: self.publish.clone(), - } - } -} - -impl<Message> Bus<Message> -where - Message: 'static, -{ - pub(crate) fn new(publish: mpsc::UnboundedSender<Message>) -> Self { - Self { - publish: Rc::new(Box::new(move |message| { - publish.unbounded_send(message).expect("Send message"); - })), - } - } - - /// Publishes a new message for the [`Application`]. - /// - /// [`Application`]: crate::Application - pub fn publish(&self, message: Message) { - (self.publish)(message) - } - - /// Creates a new [`Bus`] that applies the given function to the messages - /// before publishing. - pub fn map<B>(&self, mapper: Rc<Box<dyn Fn(B) -> Message>>) -> Bus<B> - where - B: 'static, - { - let publish = self.publish.clone(); - - Bus { - publish: Rc::new(Box::new(move |message| publish(mapper(message)))), - } - } -} diff --git a/web/src/command.rs b/web/src/command.rs deleted file mode 100644 index 33e49e70..00000000 --- a/web/src/command.rs +++ /dev/null @@ -1,72 +0,0 @@ -mod action; - -pub use action::Action; - -use std::fmt; - -#[cfg(target_arch = "wasm32")] -use std::future::Future; - -/// A set of asynchronous actions to be performed by some runtime. -pub struct Command<T>(iced_futures::Command<Action<T>>); - -impl<T> Command<T> { - /// Creates an empty [`Command`]. - /// - /// In other words, a [`Command`] that does nothing. - pub const fn none() -> Self { - Self(iced_futures::Command::none()) - } - - /// Creates a [`Command`] that performs a single [`Action`]. - pub const fn single(action: Action<T>) -> Self { - Self(iced_futures::Command::single(action)) - } - - /// Creates a [`Command`] that performs the action of the given future. - #[cfg(target_arch = "wasm32")] - pub fn perform<A>( - future: impl Future<Output = T> + 'static, - f: impl Fn(T) -> A + 'static + Send, - ) -> Command<A> { - use iced_futures::futures::FutureExt; - - Command::single(Action::Future(Box::pin(future.map(f)))) - } - - /// Creates a [`Command`] that performs the actions of all the given - /// commands. - /// - /// Once this command is run, all the commands will be executed at once. - pub fn batch(commands: impl IntoIterator<Item = Command<T>>) -> Self { - Self(iced_futures::Command::batch( - commands.into_iter().map(|Command(command)| command), - )) - } - - /// Applies a transformation to the result of a [`Command`]. - #[cfg(target_arch = "wasm32")] - pub fn map<A>(self, f: impl Fn(T) -> A + 'static + Clone) -> Command<A> - where - T: 'static, - { - let Command(command) = self; - - Command(command.map(move |action| action.map(f.clone()))) - } - - /// Returns all of the actions of the [`Command`]. - pub fn actions(self) -> Vec<Action<T>> { - let Command(command) = self; - - command.actions() - } -} - -impl<T> fmt::Debug for Command<T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let Command(command) = self; - - command.fmt(f) - } -} diff --git a/web/src/command/action.rs b/web/src/command/action.rs deleted file mode 100644 index c0223e50..00000000 --- a/web/src/command/action.rs +++ /dev/null @@ -1,28 +0,0 @@ -pub enum Action<T> { - Future(iced_futures::BoxFuture<T>), -} - -use std::fmt; - -impl<T> Action<T> { - /// Applies a transformation to the result of a [`Command`]. - #[cfg(target_arch = "wasm32")] - pub fn map<A>(self, f: impl Fn(T) -> A + 'static) -> Action<A> - where - T: 'static, - { - use iced_futures::futures::FutureExt; - - match self { - Self::Future(future) => Action::Future(Box::pin(future.map(f))), - } - } -} - -impl<T> fmt::Debug for Action<T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Future(_) => write!(f, "Action::Future"), - } - } -} diff --git a/web/src/css.rs b/web/src/css.rs deleted file mode 100644 index 07589150..00000000 --- a/web/src/css.rs +++ /dev/null @@ -1,217 +0,0 @@ -//! Style your widgets. -use crate::bumpalo; -use crate::{Alignment, Background, Color, Length, Padding}; - -use std::collections::BTreeMap; - -/// A CSS rule of a VDOM node. -#[derive(Debug)] -pub enum Rule { - /// Container with vertical distribution - Column, - - /// Container with horizonal distribution - Row, - - /// Spacing between elements - Spacing(u16), - - /// Toggler input for a specific size - Toggler(u16), -} - -impl Rule { - /// Returns the class name of the [`Rule`]. - pub fn class<'a>(&self) -> String { - match self { - Rule::Column => String::from("c"), - Rule::Row => String::from("r"), - Rule::Spacing(spacing) => format!("s-{}", spacing), - Rule::Toggler(size) => format!("toggler-{}", size), - } - } - - /// Returns the declaration of the [`Rule`]. - pub fn declaration<'a>(&self, bump: &'a bumpalo::Bump) -> &'a str { - let class = self.class(); - - match self { - Rule::Column => { - let body = "{ display: flex; flex-direction: column; }"; - - bumpalo::format!(in bump, ".{} {}", class, body).into_bump_str() - } - Rule::Row => { - let body = "{ display: flex; flex-direction: row; }"; - - bumpalo::format!(in bump, ".{} {}", class, body).into_bump_str() - } - Rule::Spacing(spacing) => bumpalo::format!( - in bump, - ".c.{} > * {{ margin-bottom: {}px }} \ - .r.{} > * {{ margin-right: {}px }} \ - .c.{} > *:last-child {{ margin-bottom: 0 }} \ - .r.{} > *:last-child {{ margin-right: 0 }}", - class, - spacing, - class, - spacing, - class, - class - ) - .into_bump_str(), - Rule::Toggler(size) => bumpalo::format!( - in bump, - ".toggler-{} {{ display: flex; cursor: pointer; justify-content: space-between; }} \ - .toggler-{} input {{ display:none; }} \ - .toggler-{} span {{ background-color: #b1b1b1; position: relative; display: inline-flex; width:{}px; height: {}px; border-radius: {}px;}} \ - .toggler-{} span > span {{ background-color: #FFFFFF; width: {}px; height: {}px; border-radius: 50%; top: 1px; left: 1px;}} \ - .toggler-{}:hover span > span {{ background-color: #f1f1f1 !important; }} \ - .toggler-{} input:checked + span {{ background-color: #00FF00; }} \ - .toggler-{} input:checked + span > span {{ -webkit-transform: translateX({}px); -ms-transform:translateX({}px); transform: translateX({}px); }} - ", - // toggler - size, - - // toggler input - size, - - // toggler span - size, - size*2, - size, - size, - - // toggler span > span - size, - size-2, - size-2, - - // toggler: hover + span > span - size, - - // toggler input:checked + span - size, - - // toggler input:checked + span > span - size, - size, - size, - size - ) - .into_bump_str(), - } - } -} - -/// A cascading style sheet. -#[derive(Debug)] -pub struct Css<'a> { - rules: BTreeMap<String, &'a str>, -} - -impl<'a> Css<'a> { - /// Creates an empty [`Css`]. - pub fn new() -> Self { - Css { - rules: BTreeMap::new(), - } - } - - /// Inserts the [`Rule`] in the [`Css`], if it was not previously - /// inserted. - /// - /// It returns the class name of the provided [`Rule`]. - pub fn insert(&mut self, bump: &'a bumpalo::Bump, rule: Rule) -> String { - let class = rule.class(); - - if !self.rules.contains_key(&class) { - let _ = self.rules.insert(class.clone(), rule.declaration(bump)); - } - - class - } - - /// Produces the VDOM node of the [`Css`]. - pub fn node(self, bump: &'a bumpalo::Bump) -> dodrio::Node<'a> { - use dodrio::builder::*; - - let mut declarations = bumpalo::collections::Vec::new_in(bump); - - declarations.push(text("html { height: 100% }")); - declarations.push(text( - "body { height: 100%; margin: 0; padding: 0; font-family: sans-serif }", - )); - declarations.push(text("* { margin: 0; padding: 0 }")); - declarations.push(text( - "button { border: none; cursor: pointer; outline: none }", - )); - - for declaration in self.rules.values() { - declarations.push(text(*declaration)); - } - - style(bump).children(declarations).finish() - } -} - -/// Returns the style value for the given [`Length`]. -pub fn length(length: Length) -> String { - match length { - Length::Shrink => String::from("auto"), - Length::Units(px) => format!("{}px", px), - Length::Fill | Length::FillPortion(_) => String::from("100%"), - } -} - -/// Returns the style value for the given maximum length in units. -pub fn max_length(units: u32) -> String { - use std::u32; - - if units == u32::MAX { - String::from("initial") - } else { - format!("{}px", units) - } -} - -/// Returns the style value for the given minimum length in units. -pub fn min_length(units: u32) -> String { - if units == 0 { - String::from("initial") - } else { - format!("{}px", units) - } -} - -/// Returns the style value for the given [`Color`]. -pub fn color(Color { r, g, b, a }: Color) -> String { - format!("rgba({}, {}, {}, {})", 255.0 * r, 255.0 * g, 255.0 * b, a) -} - -/// Returns the style value for the given [`Background`]. -pub fn background(background: Background) -> String { - match background { - Background::Color(c) => color(c), - } -} - -/// Returns the style value for the given [`Alignment`]. -pub fn alignment(alignment: Alignment) -> &'static str { - match alignment { - Alignment::Start => "flex-start", - Alignment::Center => "center", - Alignment::End => "flex-end", - Alignment::Fill => "stretch", - } -} - -/// Returns the style value for the given [`Padding`]. -/// -/// [`Padding`]: struct.Padding.html -pub fn padding(padding: Padding) -> String { - format!( - "{}px {}px {}px {}px", - padding.top, padding.right, padding.bottom, padding.left - ) -} diff --git a/web/src/element.rs b/web/src/element.rs deleted file mode 100644 index 6bb90177..00000000 --- a/web/src/element.rs +++ /dev/null @@ -1,90 +0,0 @@ -use crate::{Bus, Color, Css, Widget}; - -use dodrio::bumpalo; -use std::rc::Rc; - -/// A generic [`Widget`]. -/// -/// It is useful to build composable user interfaces that do not leak -/// implementation details in their __view logic__. -/// -/// If you have a [built-in widget], you should be able to use `Into<Element>` -/// to turn it into an [`Element`]. -/// -/// [built-in widget]: mod@crate::widget -#[allow(missing_debug_implementations)] -pub struct Element<'a, Message> { - pub(crate) widget: Box<dyn Widget<Message> + 'a>, -} - -impl<'a, Message> Element<'a, Message> { - /// Create a new [`Element`] containing the given [`Widget`]. - pub fn new(widget: impl Widget<Message> + 'a) -> Self { - Self { - widget: Box::new(widget), - } - } - - /// Applies a transformation to the produced message of the [`Element`]. - /// - /// This method is useful when you want to decouple different parts of your - /// UI and make them __composable__. - pub fn map<F, B>(self, f: F) -> Element<'a, B> - where - Message: 'static, - B: 'static, - F: 'static + Fn(Message) -> B, - { - Element { - widget: Box::new(Map::new(self.widget, f)), - } - } - - /// Marks the [`Element`] as _to-be-explained_. - pub fn explain(self, _color: Color) -> Element<'a, Message> { - self - } - - /// Produces a VDOM node for the [`Element`]. - pub fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - bus: &Bus<Message>, - style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - self.widget.node(bump, bus, style_sheet) - } -} - -struct Map<'a, A, B> { - widget: Box<dyn Widget<A> + 'a>, - mapper: Rc<Box<dyn Fn(A) -> B>>, -} - -impl<'a, A, B> Map<'a, A, B> { - pub fn new<F>(widget: Box<dyn Widget<A> + 'a>, mapper: F) -> Map<'a, A, B> - where - F: 'static + Fn(A) -> B, - { - Map { - widget, - mapper: Rc::new(Box::new(mapper)), - } - } -} - -impl<'a, A, B> Widget<B> for Map<'a, A, B> -where - A: 'static, - B: 'static, -{ - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - bus: &Bus<B>, - style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - self.widget - .node(bump, &bus.map(self.mapper.clone()), style_sheet) - } -} diff --git a/web/src/hasher.rs b/web/src/hasher.rs deleted file mode 100644 index 1a28a2f9..00000000 --- a/web/src/hasher.rs +++ /dev/null @@ -1,21 +0,0 @@ -use std::collections::hash_map::DefaultHasher; - -/// The hasher used to compare subscriptions. -#[derive(Debug)] -pub struct Hasher(DefaultHasher); - -impl Default for Hasher { - fn default() -> Self { - Hasher(DefaultHasher::default()) - } -} - -impl core::hash::Hasher for Hasher { - fn write(&mut self, bytes: &[u8]) { - self.0.write(bytes) - } - - fn finish(&self) -> u64 { - self.0.finish() - } -} diff --git a/web/src/lib.rs b/web/src/lib.rs deleted file mode 100644 index 6311dd12..00000000 --- a/web/src/lib.rs +++ /dev/null @@ -1,431 +0,0 @@ -//! A web runtime for Iced, targetting the DOM. -//! -//! `iced_web` takes [`iced_core`] and builds a WebAssembly runtime on top. It -//! achieves this by introducing a `Widget` trait that can be used to produce -//! VDOM nodes. -//! -//! The crate is currently a __very experimental__, simple abstraction layer -//! over [`dodrio`]. -//! -//! [`iced_core`]: https://github.com/iced-rs/iced/tree/master/core -//! [`dodrio`]: https://github.com/fitzgen/dodrio -//! -//! # Usage -//! The current build process is a bit involved, as [`wasm-pack`] does not -//! currently [support building binary crates](https://github.com/rustwasm/wasm-pack/issues/734). -//! -//! Therefore, we instead build using the `wasm32-unknown-unknown` target and -//! use the [`wasm-bindgen`] CLI to generate appropriate bindings. -//! -//! For instance, let's say we want to build the [`tour` example]: -//! -//! ```bash -//! cd examples -//! cargo build --package tour --target wasm32-unknown-unknown -//! wasm-bindgen ../target/wasm32-unknown-unknown/debug/tour.wasm --out-dir tour --web -//! ``` -//! -//! Then, we need to create an `.html` file to load our application: -//! -//! ```html -//! <!DOCTYPE html> -//! <html> -//! <head> -//! <meta http-equiv="Content-type" content="text/html; charset=utf-8"/> -//! <meta name="viewport" content="width=device-width, initial-scale=1"> -//! <title>Tour - Iced</title> -//! </head> -//! <body> -//! <script type="module"> -//! import init from "./tour/tour.js"; -//! -//! init('./tour/tour_bg.wasm'); -//! </script> -//! </body> -//! </html> -//! ``` -//! -//! Finally, we serve it using an HTTP server and access it with our browser. -//! -//! [`wasm-pack`]: https://github.com/rustwasm/wasm-pack -//! [`wasm-bindgen`]: https://github.com/rustwasm/wasm-bindgen -//! [`tour` example]: https://github.com/iced-rs/iced/tree/0.3/examples/tour -#![doc( - html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" -)] -#![deny(missing_docs)] -#![deny(missing_debug_implementations)] -#![deny(unused_results)] -#![forbid(unsafe_code)] -#![forbid(rust_2018_idioms)] -use dodrio::bumpalo; -use std::{cell::RefCell, rc::Rc}; - -mod bus; -mod command; -mod element; -mod hasher; - -pub mod css; -pub mod subscription; -pub mod widget; - -pub use bus::Bus; -pub use command::Command; -pub use css::Css; -pub use dodrio; -pub use element::Element; -pub use hasher::Hasher; -pub use subscription::Subscription; - -pub use iced_core::alignment; -pub use iced_core::keyboard; -pub use iced_core::mouse; -pub use iced_futures::executor; -pub use iced_futures::futures; - -pub use iced_core::{ - Alignment, Background, Color, Font, Length, Padding, Point, Rectangle, - Size, Vector, -}; - -#[doc(no_inline)] -pub use widget::*; - -#[doc(no_inline)] -pub use executor::Executor; - -/// An interactive web application. -/// -/// This trait is the main entrypoint of Iced. Once implemented, you can run -/// your GUI application by simply calling [`run`](#method.run). It will take -/// control of the `<title>` and the `<body>` of the document. -/// -/// An [`Application`](trait.Application.html) can execute asynchronous actions -/// by returning a [`Command`](struct.Command.html) in some of its methods. -pub trait Application { - /// The [`Executor`] that will run commands and subscriptions. - type Executor: Executor; - - /// The type of __messages__ your [`Application`] will produce. - type Message: Send; - - /// The data needed to initialize your [`Application`]. - type Flags; - - /// Initializes the [`Application`]. - /// - /// Here is where you should return the initial state of your app. - /// - /// Additionally, you can return a [`Command`] if you need to perform some - /// async action in the background on startup. This is useful if you want to - /// load state from a file, perform an initial HTTP request, etc. - fn new(flags: Self::Flags) -> (Self, Command<Self::Message>) - where - Self: Sized; - - /// Returns the current title of the [`Application`]. - /// - /// This title can be dynamic! The runtime will automatically update the - /// title of your application when necessary. - fn title(&self) -> String; - - /// Handles a __message__ and updates the state of the [`Application`]. - /// - /// This is where you define your __update logic__. All the __messages__, - /// produced by either user interactions or commands, will be handled by - /// this method. - /// - /// Any [`Command`] returned will be executed immediately in the background. - fn update(&mut self, message: Self::Message) -> Command<Self::Message>; - - /// Returns the widgets to display in the [`Application`]. - /// - /// These widgets can produce __messages__ based on user interaction. - fn view(&mut self) -> Element<'_, Self::Message>; - - /// Returns the event [`Subscription`] for the current state of the - /// application. - /// - /// A [`Subscription`] will be kept alive as long as you keep returning it, - /// and the __messages__ produced will be handled by - /// [`update`](#tymethod.update). - /// - /// By default, this method returns an empty [`Subscription`]. - fn subscription(&self) -> Subscription<Self::Message> { - Subscription::none() - } - - /// Runs the [`Application`]. - fn run(flags: Self::Flags) - where - Self: 'static + Sized, - { - use futures::stream::StreamExt; - - let window = web_sys::window().unwrap(); - let document = window.document().unwrap(); - let body = document.body().unwrap(); - - let (sender, receiver) = - iced_futures::futures::channel::mpsc::unbounded(); - - let mut runtime = iced_futures::Runtime::new( - Self::Executor::new().expect("Create executor"), - sender.clone(), - ); - - let (app, command) = runtime.enter(|| Self::new(flags)); - - let mut title = app.title(); - document.set_title(&title); - - run_command(command, &mut runtime); - - let application = Rc::new(RefCell::new(app)); - - let instance = Instance { - application: application.clone(), - bus: Bus::new(sender), - }; - - let vdom = dodrio::Vdom::new(&body, instance); - - let event_loop = receiver.for_each(move |message| { - let (command, subscription) = runtime.enter(|| { - let command = application.borrow_mut().update(message); - let subscription = application.borrow().subscription(); - - (command, subscription) - }); - - let new_title = application.borrow().title(); - - run_command(command, &mut runtime); - runtime.track(subscription); - - if title != new_title { - document.set_title(&new_title); - - title = new_title; - } - - vdom.weak().schedule_render(); - - futures::future::ready(()) - }); - - wasm_bindgen_futures::spawn_local(event_loop); - } -} - -struct Instance<A: Application> { - application: Rc<RefCell<A>>, - bus: Bus<A::Message>, -} - -impl<'a, A> dodrio::Render<'a> for Instance<A> -where - A: Application, -{ - fn render( - &self, - context: &mut dodrio::RenderContext<'a>, - ) -> dodrio::Node<'a> { - use dodrio::builder::*; - - let mut ui = self.application.borrow_mut(); - let element = ui.view(); - let mut css = Css::new(); - - let node = element.widget.node(context.bump, &self.bus, &mut css); - - div(context.bump) - .attr("style", "width: 100%; height: 100%") - .children(vec![css.node(context.bump), node]) - .finish() - } -} - -/// An interactive embedded web application. -/// -/// This trait is the main entrypoint of Iced. Once implemented, you can run -/// your GUI application by simply calling [`run`](#method.run). It will either -/// take control of the `<body>' or of an HTML element of the document specified -/// by `container_id`. -/// -/// An [`Embedded`](trait.Embedded.html) can execute asynchronous actions -/// by returning a [`Command`](struct.Command.html) in some of its methods. -pub trait Embedded { - /// The [`Executor`] that will run commands and subscriptions. - /// - /// The [`executor::WasmBindgen`] can be a good choice for the Web. - /// - /// [`Executor`]: trait.Executor.html - /// [`executor::Default`]: executor/struct.Default.html - type Executor: Executor; - - /// The type of __messages__ your [`Embedded`] application will produce. - /// - /// [`Embedded`]: trait.Embedded.html - type Message: Send; - - /// The data needed to initialize your [`Embedded`] application. - /// - /// [`Embedded`]: trait.Embedded.html - type Flags; - - /// Initializes the [`Embedded`] application. - /// - /// Here is where you should return the initial state of your app. - /// - /// Additionally, you can return a [`Command`](struct.Command.html) if you - /// need to perform some async action in the background on startup. This is - /// useful if you want to load state from a file, perform an initial HTTP - /// request, etc. - /// - /// [`Embedded`]: trait.Embedded.html - fn new(flags: Self::Flags) -> (Self, Command<Self::Message>) - where - Self: Sized; - - /// Handles a __message__ and updates the state of the [`Embedded`] - /// application. - /// - /// This is where you define your __update logic__. All the __messages__, - /// produced by either user interactions or commands, will be handled by - /// this method. - /// - /// Any [`Command`] returned will be executed immediately in the background. - /// - /// [`Embedded`]: trait.Embedded.html - /// [`Command`]: struct.Command.html - fn update(&mut self, message: Self::Message) -> Command<Self::Message>; - - /// Returns the widgets to display in the [`Embedded`] application. - /// - /// These widgets can produce __messages__ based on user interaction. - /// - /// [`Embedded`]: trait.Embedded.html - fn view(&mut self) -> Element<'_, Self::Message>; - - /// Returns the event [`Subscription`] for the current state of the embedded - /// application. - /// - /// A [`Subscription`] will be kept alive as long as you keep returning it, - /// and the __messages__ produced will be handled by - /// [`update`](#tymethod.update). - /// - /// By default, this method returns an empty [`Subscription`]. - /// - /// [`Subscription`]: struct.Subscription.html - fn subscription(&self) -> Subscription<Self::Message> { - Subscription::none() - } - - /// Runs the [`Embedded`] application. - /// - /// [`Embedded`]: trait.Embedded.html - fn run(flags: Self::Flags, container_id: Option<String>) - where - Self: 'static + Sized, - { - use futures::stream::StreamExt; - use wasm_bindgen::JsCast; - use web_sys::HtmlElement; - - let window = web_sys::window().unwrap(); - let document = window.document().unwrap(); - let container: HtmlElement = container_id - .map(|id| document.get_element_by_id(&id).unwrap()) - .map(|container| { - container.dyn_ref::<HtmlElement>().unwrap().to_owned() - }) - .unwrap_or_else(|| document.body().unwrap()); - - let (sender, receiver) = - iced_futures::futures::channel::mpsc::unbounded(); - - let mut runtime = iced_futures::Runtime::new( - Self::Executor::new().expect("Create executor"), - sender.clone(), - ); - - let (app, command) = runtime.enter(|| Self::new(flags)); - run_command(command, &mut runtime); - - let application = Rc::new(RefCell::new(app)); - - let instance = EmbeddedInstance { - application: application.clone(), - bus: Bus::new(sender), - }; - - let vdom = dodrio::Vdom::new(&container, instance); - - let event_loop = receiver.for_each(move |message| { - let (command, subscription) = runtime.enter(|| { - let command = application.borrow_mut().update(message); - let subscription = application.borrow().subscription(); - - (command, subscription) - }); - - run_command(command, &mut runtime); - runtime.track(subscription); - - vdom.weak().schedule_render(); - - futures::future::ready(()) - }); - - wasm_bindgen_futures::spawn_local(event_loop); - } -} - -fn run_command<Message: 'static + Send, E: Executor>( - command: Command<Message>, - runtime: &mut iced_futures::Runtime< - Hasher, - (), - E, - iced_futures::futures::channel::mpsc::UnboundedSender<Message>, - Message, - >, -) { - for action in command.actions() { - match action { - command::Action::Future(future) => { - runtime.spawn(future); - } - } - } -} - -struct EmbeddedInstance<A: Embedded> { - application: Rc<RefCell<A>>, - bus: Bus<A::Message>, -} - -impl<'a, A> dodrio::Render<'a> for EmbeddedInstance<A> -where - A: Embedded, -{ - fn render( - &self, - context: &mut dodrio::RenderContext<'a>, - ) -> dodrio::Node<'a> { - use dodrio::builder::*; - - let mut ui = self.application.borrow_mut(); - let element = ui.view(); - let mut css = Css::new(); - - let node = element.widget.node(context.bump, &self.bus, &mut css); - - div(context.bump) - .attr("style", "width: 100%; height: 100%") - .children(vec![css.node(context.bump), node]) - .finish() - } -} diff --git a/web/src/subscription.rs b/web/src/subscription.rs deleted file mode 100644 index fb54f7e3..00000000 --- a/web/src/subscription.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! Listen to external events in your application. -use crate::Hasher; - -/// A request to listen to external events. -/// -/// Besides performing async actions on demand with [`Command`], most -/// applications also need to listen to external events passively. -/// -/// A [`Subscription`] is normally provided to some runtime, like a [`Command`], -/// and it will generate events as long as the user keeps requesting it. -/// -/// For instance, you can use a [`Subscription`] to listen to a WebSocket -/// connection, keyboard presses, mouse events, time ticks, etc. -/// -/// [`Command`]: crate::Command -pub type Subscription<T> = iced_futures::Subscription<Hasher, (), T>; - -pub use iced_futures::subscription::Recipe; diff --git a/web/src/widget.rs b/web/src/widget.rs deleted file mode 100644 index 4cb0a9cc..00000000 --- a/web/src/widget.rs +++ /dev/null @@ -1,68 +0,0 @@ -//! Use the built-in widgets or create your own. -//! -//! # Custom widgets -//! If you want to implement a custom widget, you simply need to implement the -//! [`Widget`] trait. You can use the API of the built-in widgets as a guide or -//! source of inspiration. -//! -//! # Re-exports -//! For convenience, the contents of this module are available at the root -//! module. Therefore, you can directly type: -//! -//! ``` -//! use iced_web::{button, Button, Widget}; -//! ``` -use crate::{Bus, Css}; -use dodrio::bumpalo; - -pub mod button; -pub mod checkbox; -pub mod container; -pub mod image; -pub mod progress_bar; -pub mod radio; -pub mod scrollable; -pub mod slider; -pub mod text_input; -pub mod toggler; - -mod column; -mod row; -mod space; -mod text; - -#[doc(no_inline)] -pub use button::Button; -#[doc(no_inline)] -pub use scrollable::Scrollable; -#[doc(no_inline)] -pub use slider::Slider; -#[doc(no_inline)] -pub use text::Text; -#[doc(no_inline)] -pub use text_input::TextInput; -#[doc(no_inline)] -pub use toggler::Toggler; - -pub use checkbox::Checkbox; -pub use column::Column; -pub use container::Container; -pub use image::Image; -pub use progress_bar::ProgressBar; -pub use radio::Radio; -pub use row::Row; -pub use space::Space; - -/// A component that displays information and allows interaction. -/// -/// If you want to build your own widgets, you will need to implement this -/// trait. -pub trait Widget<Message> { - /// Produces a VDOM node for the [`Widget`]. - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - _bus: &Bus<Message>, - style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b>; -} diff --git a/web/src/widget/button.rs b/web/src/widget/button.rs deleted file mode 100644 index 88137607..00000000 --- a/web/src/widget/button.rs +++ /dev/null @@ -1,192 +0,0 @@ -//! Allow your users to perform actions by pressing a button. -//! -//! A [`Button`] has some local [`State`]. -use crate::{css, Background, Bus, Css, Element, Length, Padding, Widget}; - -pub use iced_style::button::{Style, StyleSheet}; - -use dodrio::bumpalo; - -/// A generic widget that produces a message when pressed. -/// -/// ``` -/// # use iced_web::{button, Button, Text}; -/// # -/// enum Message { -/// ButtonPressed, -/// } -/// -/// let mut state = button::State::new(); -/// let button = Button::new(&mut state, Text::new("Press me!")) -/// .on_press(Message::ButtonPressed); -/// ``` -/// -/// If a [`Button::on_press`] handler is not set, the resulting [`Button`] will -/// be disabled: -/// -/// ``` -/// # use iced_web::{button, Button, Text}; -/// # -/// #[derive(Clone)] -/// enum Message { -/// ButtonPressed, -/// } -/// -/// fn disabled_button(state: &mut button::State) -> Button<'_, Message> { -/// Button::new(state, Text::new("I'm disabled!")) -/// } -/// -/// fn enabled_button(state: &mut button::State) -> Button<'_, Message> { -/// disabled_button(state).on_press(Message::ButtonPressed) -/// } -/// ``` -#[allow(missing_debug_implementations)] -pub struct Button<'a, Message> { - content: Element<'a, Message>, - on_press: Option<Message>, - width: Length, - #[allow(dead_code)] - height: Length, - min_width: u32, - #[allow(dead_code)] - min_height: u32, - padding: Padding, - style: Box<dyn StyleSheet + 'a>, -} - -impl<'a, Message> Button<'a, Message> { - /// Creates a new [`Button`] with some local [`State`] and the given - /// content. - pub fn new<E>(_state: &'a mut State, content: E) -> Self - where - E: Into<Element<'a, Message>>, - { - Button { - content: content.into(), - on_press: None, - width: Length::Shrink, - height: Length::Shrink, - min_width: 0, - min_height: 0, - padding: Padding::new(5), - style: Default::default(), - } - } - - /// Sets the width of the [`Button`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Button`]. - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } - - /// Sets the minimum width of the [`Button`]. - pub fn min_width(mut self, min_width: u32) -> Self { - self.min_width = min_width; - self - } - - /// Sets the minimum height of the [`Button`]. - pub fn min_height(mut self, min_height: u32) -> Self { - self.min_height = min_height; - self - } - - /// Sets the [`Padding`] of the [`Button`]. - pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - /// Sets the style of the [`Button`]. - pub fn style(mut self, style: impl Into<Box<dyn StyleSheet + 'a>>) -> Self { - self.style = style.into(); - self - } - - /// Sets the message that will be produced when the [`Button`] is pressed. - /// If on_press isn't set, button will be disabled. - pub fn on_press(mut self, msg: Message) -> Self { - self.on_press = Some(msg); - self - } -} - -/// The local state of a [`Button`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct State; - -impl State { - /// Creates a new [`State`]. - pub fn new() -> State { - State::default() - } -} - -impl<'a, Message> Widget<Message> for Button<'a, Message> -where - Message: 'static + Clone, -{ - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - bus: &Bus<Message>, - style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - - // TODO: State-based styling - let style = self.style.active(); - - let background = match style.background { - None => String::from("none"), - Some(background) => match background { - Background::Color(color) => css::color(color), - }, - }; - - let mut node = button(bump) - .attr( - "style", - bumpalo::format!( - in bump, - "background: {}; border-radius: {}px; width:{}; \ - min-width: {}; color: {}; padding: {}", - background, - style.border_radius, - css::length(self.width), - css::min_length(self.min_width), - css::color(style.text_color), - css::padding(self.padding) - ) - .into_bump_str(), - ) - .children(vec![self.content.node(bump, bus, style_sheet)]); - - if let Some(on_press) = self.on_press.clone() { - let event_bus = bus.clone(); - - node = node.on("click", move |_root, _vdom, _event| { - event_bus.publish(on_press.clone()); - }); - } else { - node = node.attr("disabled", ""); - } - - node.finish() - } -} - -impl<'a, Message> From<Button<'a, Message>> for Element<'a, Message> -where - Message: 'static + Clone, -{ - fn from(button: Button<'a, Message>) -> Element<'a, Message> { - Element::new(button) - } -} diff --git a/web/src/widget/checkbox.rs b/web/src/widget/checkbox.rs deleted file mode 100644 index 844bf862..00000000 --- a/web/src/widget/checkbox.rs +++ /dev/null @@ -1,150 +0,0 @@ -//! Show toggle controls using checkboxes. -use crate::{css, Bus, Css, Element, Length, Widget}; - -pub use iced_style::checkbox::{Style, StyleSheet}; - -use dodrio::bumpalo; -use std::rc::Rc; - -/// A box that can be checked. -/// -/// # Example -/// -/// ``` -/// # use iced_web::Checkbox; -/// -/// pub enum Message { -/// CheckboxToggled(bool), -/// } -/// -/// let is_checked = true; -/// -/// Checkbox::new(is_checked, "Toggle me!", Message::CheckboxToggled); -/// ``` -/// -///  -#[allow(missing_debug_implementations)] -pub struct Checkbox<'a, Message> { - is_checked: bool, - on_toggle: Rc<dyn Fn(bool) -> Message>, - label: String, - id: Option<String>, - width: Length, - #[allow(dead_code)] - style_sheet: Box<dyn StyleSheet + 'a>, -} - -impl<'a, Message> Checkbox<'a, Message> { - /// Creates a new [`Checkbox`]. - /// - /// It expects: - /// * a boolean describing whether the [`Checkbox`] is checked or not - /// * the label of the [`Checkbox`] - /// * a function that will be called when the [`Checkbox`] is toggled. It - /// will receive the new state of the [`Checkbox`] and must produce a - /// `Message`. - pub fn new<F>(is_checked: bool, label: impl Into<String>, f: F) -> Self - where - F: 'static + Fn(bool) -> Message, - { - Checkbox { - is_checked, - on_toggle: Rc::new(f), - label: label.into(), - id: None, - width: Length::Shrink, - style_sheet: Default::default(), - } - } - - /// Sets the width of the [`Checkbox`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the style of the [`Checkbox`]. - pub fn style( - mut self, - style_sheet: impl Into<Box<dyn StyleSheet + 'a>>, - ) -> Self { - self.style_sheet = style_sheet.into(); - self - } - - /// Sets the id of the [`Checkbox`]. - pub fn id(mut self, id: impl Into<String>) -> Self { - self.id = Some(id.into()); - self - } -} - -impl<'a, Message> Widget<Message> for Checkbox<'a, Message> -where - Message: 'static, -{ - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - bus: &Bus<Message>, - style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - use dodrio::bumpalo::collections::String; - - let checkbox_label = - String::from_str_in(&self.label, bump).into_bump_str(); - - let event_bus = bus.clone(); - let on_toggle = self.on_toggle.clone(); - let is_checked = self.is_checked; - - let row_class = style_sheet.insert(bump, css::Rule::Row); - - let spacing_class = style_sheet.insert(bump, css::Rule::Spacing(5)); - - let (label, input) = if let Some(id) = &self.id { - let id = String::from_str_in(id, bump).into_bump_str(); - - (label(bump).attr("for", id), input(bump).attr("id", id)) - } else { - (label(bump), input(bump)) - }; - - label - .attr( - "class", - bumpalo::format!(in bump, "{} {}", row_class, spacing_class) - .into_bump_str(), - ) - .attr( - "style", - bumpalo::format!(in bump, "width: {}; align-items: center", css::length(self.width)) - .into_bump_str(), - ) - .children(vec![ - // TODO: Checkbox styling - input - .attr("type", "checkbox") - .bool_attr("checked", self.is_checked) - .on("click", move |_root, vdom, _event| { - let msg = on_toggle(!is_checked); - event_bus.publish(msg); - - vdom.schedule_render(); - }) - .finish(), - text(checkbox_label), - ]) - .finish() - } -} - -impl<'a, Message> From<Checkbox<'a, Message>> for Element<'a, Message> -where - Message: 'static, -{ - fn from(checkbox: Checkbox<'a, Message>) -> Element<'a, Message> { - Element::new(checkbox) - } -} diff --git a/web/src/widget/column.rs b/web/src/widget/column.rs deleted file mode 100644 index 30a57c41..00000000 --- a/web/src/widget/column.rs +++ /dev/null @@ -1,148 +0,0 @@ -use crate::css; -use crate::{Alignment, Bus, Css, Element, Length, Padding, Widget}; - -use dodrio::bumpalo; -use std::u32; - -/// A container that distributes its contents vertically. -/// -/// A [`Column`] will try to fill the horizontal space of its container. -#[allow(missing_debug_implementations)] -pub struct Column<'a, Message> { - spacing: u16, - padding: Padding, - width: Length, - height: Length, - max_width: u32, - max_height: u32, - align_items: Alignment, - children: Vec<Element<'a, Message>>, -} - -impl<'a, Message> Column<'a, Message> { - /// Creates an empty [`Column`]. - pub fn new() -> Self { - Self::with_children(Vec::new()) - } - - /// Creates a [`Column`] with the given elements. - pub fn with_children(children: Vec<Element<'a, Message>>) -> Self { - Column { - spacing: 0, - padding: Padding::ZERO, - width: Length::Fill, - height: Length::Shrink, - max_width: u32::MAX, - max_height: u32::MAX, - align_items: Alignment::Start, - children, - } - } - - /// Sets the vertical spacing _between_ elements. - /// - /// Custom margins per element do not exist in Iced. You should use this - /// method instead! While less flexible, it helps you keep spacing between - /// elements consistent. - pub fn spacing(mut self, units: u16) -> Self { - self.spacing = units; - self - } - - /// Sets the [`Padding`] of the [`Column`]. - pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - /// Sets the width of the [`Column`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Column`]. - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } - - /// Sets the maximum width of the [`Column`]. - pub fn max_width(mut self, max_width: u32) -> Self { - self.max_width = max_width; - self - } - - /// Sets the maximum height of the [`Column`] in pixels. - pub fn max_height(mut self, max_height: u32) -> Self { - self.max_height = max_height; - self - } - - /// Sets the horizontal alignment of the contents of the [`Column`] . - pub fn align_items(mut self, align: Alignment) -> Self { - self.align_items = align; - self - } - - /// Adds an element to the [`Column`]. - pub fn push<E>(mut self, child: E) -> Self - where - E: Into<Element<'a, Message>>, - { - self.children.push(child.into()); - self - } -} - -impl<'a, Message> Widget<Message> for Column<'a, Message> { - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - publish: &Bus<Message>, - style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - - let children: Vec<_> = self - .children - .iter() - .map(|element| element.widget.node(bump, publish, style_sheet)) - .collect(); - - let column_class = style_sheet.insert(bump, css::Rule::Column); - - let spacing_class = - style_sheet.insert(bump, css::Rule::Spacing(self.spacing)); - - // TODO: Complete styling - div(bump) - .attr( - "class", - bumpalo::format!(in bump, "{} {}", column_class, spacing_class) - .into_bump_str(), - ) - .attr("style", bumpalo::format!( - in bump, - "width: {}; height: {}; max-width: {}; max-height: {}; padding: {}; align-items: {}", - css::length(self.width), - css::length(self.height), - css::max_length(self.max_width), - css::max_length(self.max_height), - css::padding(self.padding), - css::alignment(self.align_items) - ).into_bump_str() - ) - .children(children) - .finish() - } -} - -impl<'a, Message> From<Column<'a, Message>> for Element<'a, Message> -where - Message: 'static, -{ - fn from(column: Column<'a, Message>) -> Element<'a, Message> { - Element::new(column) - } -} diff --git a/web/src/widget/container.rs b/web/src/widget/container.rs deleted file mode 100644 index 8e345b9a..00000000 --- a/web/src/widget/container.rs +++ /dev/null @@ -1,153 +0,0 @@ -//! Decorate content and apply alignment. -use crate::alignment::{self, Alignment}; -use crate::bumpalo; -use crate::css; -use crate::{Bus, Css, Element, Length, Padding, Widget}; - -pub use iced_style::container::{Style, StyleSheet}; - -/// An element decorating some content. -/// -/// It is normally used for alignment purposes. -#[allow(missing_debug_implementations)] -pub struct Container<'a, Message> { - padding: Padding, - width: Length, - height: Length, - max_width: u32, - #[allow(dead_code)] - max_height: u32, - horizontal_alignment: alignment::Horizontal, - vertical_alignment: alignment::Vertical, - style_sheet: Box<dyn StyleSheet + 'a>, - content: Element<'a, Message>, -} - -impl<'a, Message> Container<'a, Message> { - /// Creates an empty [`Container`]. - pub fn new<T>(content: T) -> Self - where - T: Into<Element<'a, Message>>, - { - use std::u32; - - Container { - padding: Padding::ZERO, - width: Length::Shrink, - height: Length::Shrink, - max_width: u32::MAX, - max_height: u32::MAX, - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, - style_sheet: Default::default(), - content: content.into(), - } - } - - /// Sets the [`Padding`] of the [`Container`]. - pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - /// Sets the width of the [`Container`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Container`]. - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } - - /// Sets the maximum width of the [`Container`]. - pub fn max_width(mut self, max_width: u32) -> Self { - self.max_width = max_width; - self - } - - /// Sets the maximum height of the [`Container`] in pixels. - pub fn max_height(mut self, max_height: u32) -> Self { - self.max_height = max_height; - self - } - - /// Centers the contents in the horizontal axis of the [`Container`]. - pub fn center_x(mut self) -> Self { - self.horizontal_alignment = alignment::Horizontal::Center; - - self - } - - /// Centers the contents in the vertical axis of the [`Container`]. - pub fn center_y(mut self) -> Self { - self.vertical_alignment = alignment::Vertical::Center; - - self - } - - /// Sets the style of the [`Container`]. - pub fn style(mut self, style: impl Into<Box<dyn StyleSheet + 'a>>) -> Self { - self.style_sheet = style.into(); - self - } -} - -impl<'a, Message> Widget<Message> for Container<'a, Message> -where - Message: 'static, -{ - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - bus: &Bus<Message>, - style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - - let column_class = style_sheet.insert(bump, css::Rule::Column); - - let style = self.style_sheet.style(); - - let node = div(bump) - .attr( - "class", - bumpalo::format!(in bump, "{}", column_class).into_bump_str(), - ) - .attr( - "style", - bumpalo::format!( - in bump, - "width: {}; height: {}; max-width: {}; padding: {}; align-items: {}; justify-content: {}; background: {}; color: {}; border-width: {}px; border-color: {}; border-radius: {}px", - css::length(self.width), - css::length(self.height), - css::max_length(self.max_width), - css::padding(self.padding), - css::alignment(Alignment::from(self.horizontal_alignment)), - css::alignment(Alignment::from(self.vertical_alignment)), - style.background.map(css::background).unwrap_or(String::from("initial")), - style.text_color.map(css::color).unwrap_or(String::from("inherit")), - style.border_width, - css::color(style.border_color), - style.border_radius - ) - .into_bump_str(), - ) - .children(vec![self.content.node(bump, bus, style_sheet)]); - - // TODO: Complete styling - - node.finish() - } -} - -impl<'a, Message> From<Container<'a, Message>> for Element<'a, Message> -where - Message: 'static, -{ - fn from(container: Container<'a, Message>) -> Element<'a, Message> { - Element::new(container) - } -} diff --git a/web/src/widget/image.rs b/web/src/widget/image.rs deleted file mode 100644 index 28435f4f..00000000 --- a/web/src/widget/image.rs +++ /dev/null @@ -1,186 +0,0 @@ -//! Display images in your user interface. -use crate::{Bus, Css, Element, Hasher, Length, Widget}; - -use dodrio::bumpalo; -use std::{ - hash::{Hash, Hasher as _}, - path::PathBuf, - sync::Arc, -}; - -/// A frame that displays an image while keeping aspect ratio. -/// -/// # Example -/// -/// ``` -/// # use iced_web::Image; -/// -/// let image = Image::new("resources/ferris.png"); -/// ``` -#[derive(Debug)] -pub struct Image { - /// The image path - pub handle: Handle, - - /// The alt text of the image - pub alt: String, - - /// The width of the image - pub width: Length, - - /// The height of the image - pub height: Length, -} - -impl Image { - /// Creates a new [`Image`] with the given path. - pub fn new<T: Into<Handle>>(handle: T) -> Self { - Image { - handle: handle.into(), - alt: Default::default(), - width: Length::Shrink, - height: Length::Shrink, - } - } - - /// Sets the width of the [`Image`] boundaries. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Image`] boundaries. - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } - - /// Sets the alt text of the [`Image`]. - pub fn alt(mut self, alt: impl Into<String>) -> Self { - self.alt = alt.into(); - self - } -} - -impl<Message> Widget<Message> for Image { - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - _bus: &Bus<Message>, - _style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - use dodrio::bumpalo::collections::String; - - let src = match self.handle.data.as_ref() { - Data::Path(path) => { - String::from_str_in(path.to_str().unwrap_or(""), bump) - } - Data::Bytes(bytes) => { - // The web is able to infer the kind of image, so we don't have to add a dependency on image-rs to guess the mime type. - bumpalo::format!(in bump, "data:;base64,{}", base64::encode(bytes)) - }, - } - .into_bump_str(); - - let alt = String::from_str_in(&self.alt, bump).into_bump_str(); - - let mut image = img(bump).attr("src", src).attr("alt", alt); - - match self.width { - Length::Shrink => {} - Length::Fill | Length::FillPortion(_) => { - image = image.attr("width", "100%"); - } - Length::Units(px) => { - image = image.attr( - "width", - bumpalo::format!(in bump, "{}px", px).into_bump_str(), - ); - } - } - - // TODO: Complete styling - - image.finish() - } -} - -impl<'a, Message> From<Image> for Element<'a, Message> { - fn from(image: Image) -> Element<'a, Message> { - Element::new(image) - } -} - -/// An [`Image`] handle. -#[derive(Debug, Clone)] -pub struct Handle { - id: u64, - data: Arc<Data>, -} - -impl Handle { - /// Creates an image [`Handle`] pointing to the image of the given path. - pub fn from_path<T: Into<PathBuf>>(path: T) -> Handle { - Self::from_data(Data::Path(path.into())) - } - - /// Creates an image [`Handle`] containing the image data directly. - /// - /// This is useful if you already have your image loaded in-memory, maybe - /// because you downloaded or generated it procedurally. - pub fn from_memory(bytes: Vec<u8>) -> Handle { - Self::from_data(Data::Bytes(bytes)) - } - - fn from_data(data: Data) -> Handle { - let mut hasher = Hasher::default(); - data.hash(&mut hasher); - - Handle { - id: hasher.finish(), - data: Arc::new(data), - } - } - - /// Returns the unique identifier of the [`Handle`]. - pub fn id(&self) -> u64 { - self.id - } - - /// Returns a reference to the image [`Data`]. - pub fn data(&self) -> &Data { - &self.data - } -} - -impl From<String> for Handle { - fn from(path: String) -> Handle { - Handle::from_path(path) - } -} - -impl From<&str> for Handle { - fn from(path: &str) -> Handle { - Handle::from_path(path) - } -} - -/// The data of an [`Image`]. -#[derive(Clone, Hash)] -pub enum Data { - /// A remote image - Path(PathBuf), - - /// In-memory data - Bytes(Vec<u8>), -} - -impl std::fmt::Debug for Data { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Data::Path(path) => write!(f, "Path({:?})", path), - Data::Bytes(_) => write!(f, "Bytes(...)"), - } - } -} diff --git a/web/src/widget/progress_bar.rs b/web/src/widget/progress_bar.rs deleted file mode 100644 index 01f412f8..00000000 --- a/web/src/widget/progress_bar.rs +++ /dev/null @@ -1,116 +0,0 @@ -//! Provide progress feedback to your users. -use crate::{bumpalo, css, Bus, Css, Element, Length, Widget}; - -pub use iced_style::progress_bar::{Style, StyleSheet}; - -use std::ops::RangeInclusive; - -/// A bar that displays progress. -/// -/// # Example -/// ``` -/// use iced_web::ProgressBar; -/// -/// let value = 50.0; -/// -/// ProgressBar::new(0.0..=100.0, value); -/// ``` -/// -///  -#[allow(missing_debug_implementations)] -pub struct ProgressBar<'a> { - range: RangeInclusive<f32>, - value: f32, - width: Length, - height: Option<Length>, - style: Box<dyn StyleSheet + 'a>, -} - -impl<'a> ProgressBar<'a> { - /// Creates a new [`ProgressBar`]. - /// - /// It expects: - /// * an inclusive range of possible values - /// * the current value of the [`ProgressBar`] - pub fn new(range: RangeInclusive<f32>, value: f32) -> Self { - ProgressBar { - value: value.max(*range.start()).min(*range.end()), - range, - width: Length::Fill, - height: None, - style: Default::default(), - } - } - - /// Sets the width of the [`ProgressBar`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`ProgressBar`]. - pub fn height(mut self, height: Length) -> Self { - self.height = Some(height); - self - } - - /// Sets the style of the [`ProgressBar`]. - pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self { - self.style = style.into(); - self - } -} - -impl<'a, Message> Widget<Message> for ProgressBar<'a> { - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - _bus: &Bus<Message>, - _style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - - let (range_start, range_end) = self.range.clone().into_inner(); - let amount_filled = - (self.value - range_start) / (range_end - range_start).max(1.0); - - let style = self.style.style(); - - let bar = div(bump) - .attr( - "style", - bumpalo::format!( - in bump, - "width: {}%; height: 100%; background: {}", - amount_filled * 100.0, - css::background(style.bar) - ) - .into_bump_str(), - ) - .finish(); - - let node = div(bump).attr( - "style", - bumpalo::format!( - in bump, - "width: {}; height: {}; background: {}; border-radius: {}px; overflow: hidden;", - css::length(self.width), - css::length(self.height.unwrap_or(Length::Units(30))), - css::background(style.background), - style.border_radius - ) - .into_bump_str(), - ).children(vec![bar]); - - node.finish() - } -} - -impl<'a, Message> From<ProgressBar<'a>> for Element<'a, Message> -where - Message: 'static, -{ - fn from(container: ProgressBar<'a>) -> Element<'a, Message> { - Element::new(container) - } -} diff --git a/web/src/widget/radio.rs b/web/src/widget/radio.rs deleted file mode 100644 index 03b2922b..00000000 --- a/web/src/widget/radio.rs +++ /dev/null @@ -1,155 +0,0 @@ -//! Create choices using radio buttons. -use crate::{Bus, Css, Element, Widget}; - -pub use iced_style::radio::{Style, StyleSheet}; - -use dodrio::bumpalo; - -/// A circular button representing a choice. -/// -/// # Example -/// ``` -/// # use iced_web::Radio; -/// -/// #[derive(Debug, Clone, Copy, PartialEq, Eq)] -/// pub enum Choice { -/// A, -/// B, -/// } -/// -/// #[derive(Debug, Clone, Copy)] -/// pub enum Message { -/// RadioSelected(Choice), -/// } -/// -/// let selected_choice = Some(Choice::A); -/// -/// Radio::new(Choice::A, "This is A", selected_choice, Message::RadioSelected); -/// -/// Radio::new(Choice::B, "This is B", selected_choice, Message::RadioSelected); -/// ``` -/// -///  -#[allow(missing_debug_implementations)] -pub struct Radio<'a, Message> { - is_selected: bool, - on_click: Message, - label: String, - id: Option<String>, - name: Option<String>, - #[allow(dead_code)] - style_sheet: Box<dyn StyleSheet + 'a>, -} - -impl<'a, Message> Radio<'a, Message> { - /// Creates a new [`Radio`] button. - /// - /// It expects: - /// * the value related to the [`Radio`] button - /// * the label of the [`Radio`] button - /// * the current selected value - /// * a function that will be called when the [`Radio`] is selected. It - /// receives the value of the radio and must produce a `Message`. - pub fn new<F, V>( - value: V, - label: impl Into<String>, - selected: Option<V>, - f: F, - ) -> Self - where - V: Eq + Copy, - F: 'static + Fn(V) -> Message, - { - Radio { - is_selected: Some(value) == selected, - on_click: f(value), - label: label.into(), - id: None, - name: None, - style_sheet: Default::default(), - } - } - - /// Sets the style of the [`Radio`] button. - pub fn style( - mut self, - style_sheet: impl Into<Box<dyn StyleSheet + 'a>>, - ) -> Self { - self.style_sheet = style_sheet.into(); - self - } - - /// Sets the name attribute of the [`Radio`] button. - pub fn name(mut self, name: impl Into<String>) -> Self { - self.name = Some(name.into()); - self - } - - /// Sets the id of the [`Radio`] button. - pub fn id(mut self, id: impl Into<String>) -> Self { - self.id = Some(id.into()); - self - } -} - -impl<'a, Message> Widget<Message> for Radio<'a, Message> -where - Message: 'static + Clone, -{ - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - bus: &Bus<Message>, - _style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - use dodrio::bumpalo::collections::String; - - let radio_label = - String::from_str_in(&self.label, bump).into_bump_str(); - - let event_bus = bus.clone(); - let on_click = self.on_click.clone(); - - let (label, input) = if let Some(id) = &self.id { - let id = String::from_str_in(id, bump).into_bump_str(); - - (label(bump).attr("for", id), input(bump).attr("id", id)) - } else { - (label(bump), input(bump)) - }; - - let input = if let Some(name) = &self.name { - let name = String::from_str_in(name, bump).into_bump_str(); - - dodrio::builder::input(bump).attr("name", name) - } else { - input - }; - - // TODO: Complete styling - label - .attr("style", "display: block; font-size: 20px") - .children(vec![ - input - .attr("type", "radio") - .attr("style", "margin-right: 10px") - .bool_attr("checked", self.is_selected) - .on("click", move |_root, _vdom, _event| { - event_bus.publish(on_click.clone()); - }) - .finish(), - text(radio_label), - ]) - .finish() - } -} - -impl<'a, Message> From<Radio<'a, Message>> for Element<'a, Message> -where - Message: 'static + Clone, -{ - fn from(radio: Radio<'a, Message>) -> Element<'a, Message> { - Element::new(radio) - } -} diff --git a/web/src/widget/row.rs b/web/src/widget/row.rs deleted file mode 100644 index 13eab27d..00000000 --- a/web/src/widget/row.rs +++ /dev/null @@ -1,148 +0,0 @@ -use crate::css; -use crate::{Alignment, Bus, Css, Element, Length, Padding, Widget}; - -use dodrio::bumpalo; -use std::u32; - -/// A container that distributes its contents horizontally. -/// -/// A [`Row`] will try to fill the horizontal space of its container. -#[allow(missing_debug_implementations)] -pub struct Row<'a, Message> { - spacing: u16, - padding: Padding, - width: Length, - height: Length, - max_width: u32, - max_height: u32, - align_items: Alignment, - children: Vec<Element<'a, Message>>, -} - -impl<'a, Message> Row<'a, Message> { - /// Creates an empty [`Row`]. - pub fn new() -> Self { - Self::with_children(Vec::new()) - } - - /// Creates a [`Row`] with the given elements. - pub fn with_children(children: Vec<Element<'a, Message>>) -> Self { - Row { - spacing: 0, - padding: Padding::ZERO, - width: Length::Fill, - height: Length::Shrink, - max_width: u32::MAX, - max_height: u32::MAX, - align_items: Alignment::Start, - children, - } - } - - /// Sets the horizontal spacing _between_ elements. - /// - /// Custom margins per element do not exist in Iced. You should use this - /// method instead! While less flexible, it helps you keep spacing between - /// elements consistent. - pub fn spacing(mut self, units: u16) -> Self { - self.spacing = units; - self - } - - /// Sets the [`Padding`] of the [`Row`]. - pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - /// Sets the width of the [`Row`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Row`]. - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } - - /// Sets the maximum width of the [`Row`]. - pub fn max_width(mut self, max_width: u32) -> Self { - self.max_width = max_width; - self - } - - /// Sets the maximum height of the [`Row`]. - pub fn max_height(mut self, max_height: u32) -> Self { - self.max_height = max_height; - self - } - - /// Sets the vertical alignment of the contents of the [`Row`] . - pub fn align_items(mut self, align: Alignment) -> Self { - self.align_items = align; - self - } - - /// Adds an [`Element`] to the [`Row`]. - pub fn push<E>(mut self, child: E) -> Self - where - E: Into<Element<'a, Message>>, - { - self.children.push(child.into()); - self - } -} - -impl<'a, Message> Widget<Message> for Row<'a, Message> { - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - publish: &Bus<Message>, - style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - - let children: Vec<_> = self - .children - .iter() - .map(|element| element.widget.node(bump, publish, style_sheet)) - .collect(); - - let row_class = style_sheet.insert(bump, css::Rule::Row); - - let spacing_class = - style_sheet.insert(bump, css::Rule::Spacing(self.spacing)); - - // TODO: Complete styling - div(bump) - .attr( - "class", - bumpalo::format!(in bump, "{} {}", row_class, spacing_class) - .into_bump_str(), - ) - .attr("style", bumpalo::format!( - in bump, - "width: {}; height: {}; max-width: {}; max-height: {}; padding: {}; align-items: {}", - css::length(self.width), - css::length(self.height), - css::max_length(self.max_width), - css::max_length(self.max_height), - css::padding(self.padding), - css::alignment(self.align_items) - ).into_bump_str() - ) - .children(children) - .finish() - } -} - -impl<'a, Message> From<Row<'a, Message>> for Element<'a, Message> -where - Message: 'static, -{ - fn from(column: Row<'a, Message>) -> Element<'a, Message> { - Element::new(column) - } -} diff --git a/web/src/widget/scrollable.rs b/web/src/widget/scrollable.rs deleted file mode 100644 index 22cb61be..00000000 --- a/web/src/widget/scrollable.rs +++ /dev/null @@ -1,152 +0,0 @@ -//! Navigate an endless amount of content with a scrollbar. -use crate::bumpalo; -use crate::css; -use crate::{Alignment, Bus, Column, Css, Element, Length, Padding, Widget}; - -pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet}; - -/// A widget that can vertically display an infinite amount of content with a -/// scrollbar. -#[allow(missing_debug_implementations)] -pub struct Scrollable<'a, Message> { - width: Length, - height: Length, - max_height: u32, - content: Column<'a, Message>, - #[allow(dead_code)] - style_sheet: Box<dyn StyleSheet + 'a>, -} - -impl<'a, Message> Scrollable<'a, Message> { - /// Creates a new [`Scrollable`] with the given [`State`]. - pub fn new(_state: &'a mut State) -> Self { - use std::u32; - - Scrollable { - width: Length::Fill, - height: Length::Shrink, - max_height: u32::MAX, - content: Column::new(), - style_sheet: Default::default(), - } - } - - /// Sets the vertical spacing _between_ elements. - /// - /// Custom margins per element do not exist in Iced. You should use this - /// method instead! While less flexible, it helps you keep spacing between - /// elements consistent. - pub fn spacing(mut self, units: u16) -> Self { - self.content = self.content.spacing(units); - self - } - - /// Sets the [`Padding`] of the [`Scrollable`]. - pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { - self.content = self.content.padding(padding); - self - } - - /// Sets the width of the [`Scrollable`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Scrollable`]. - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } - - /// Sets the maximum width of the [`Scrollable`]. - pub fn max_width(mut self, max_width: u32) -> Self { - self.content = self.content.max_width(max_width); - self - } - - /// Sets the maximum height of the [`Scrollable`] in pixels. - pub fn max_height(mut self, max_height: u32) -> Self { - self.max_height = max_height; - self - } - - /// Sets the horizontal alignment of the contents of the [`Scrollable`] . - pub fn align_items(mut self, align_items: Alignment) -> Self { - self.content = self.content.align_items(align_items); - self - } - - /// Sets the style of the [`Scrollable`] . - pub fn style( - mut self, - style_sheet: impl Into<Box<dyn StyleSheet + 'a>>, - ) -> Self { - self.style_sheet = style_sheet.into(); - self - } - - /// Adds an element to the [`Scrollable`]. - pub fn push<E>(mut self, child: E) -> Self - where - E: Into<Element<'a, Message>>, - { - self.content = self.content.push(child); - self - } -} - -impl<'a, Message> Widget<Message> for Scrollable<'a, Message> -where - Message: 'static, -{ - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - bus: &Bus<Message>, - style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - - let width = css::length(self.width); - let height = css::length(self.height); - - // TODO: Scrollbar styling - - let node = div(bump) - .attr( - "style", - bumpalo::format!( - in bump, - "width: {}; height: {}; max-height: {}px; overflow: auto", - width, - height, - self.max_height - ) - .into_bump_str(), - ) - .children(vec![self.content.node(bump, bus, style_sheet)]); - - node.finish() - } -} - -impl<'a, Message> From<Scrollable<'a, Message>> for Element<'a, Message> -where - Message: 'static, -{ - fn from(scrollable: Scrollable<'a, Message>) -> Element<'a, Message> { - Element::new(scrollable) - } -} - -/// The local state of a [`Scrollable`]. -#[derive(Debug, Clone, Copy, Default)] -pub struct State; - -impl State { - /// Creates a new [`State`] with the scrollbar located at the top. - pub fn new() -> Self { - State::default() - } -} diff --git a/web/src/widget/slider.rs b/web/src/widget/slider.rs deleted file mode 100644 index 8cbf5bd0..00000000 --- a/web/src/widget/slider.rs +++ /dev/null @@ -1,183 +0,0 @@ -//! Display an interactive selector of a single value from a range of values. -//! -//! A [`Slider`] has some local [`State`]. -use crate::{Bus, Css, Element, Length, Widget}; - -pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet}; - -use dodrio::bumpalo; -use std::{ops::RangeInclusive, rc::Rc}; - -/// An horizontal bar and a handle that selects a single value from a range of -/// values. -/// -/// A [`Slider`] will try to fill the horizontal space of its container. -/// -/// The [`Slider`] range of numeric values is generic and its step size defaults -/// to 1 unit. -/// -/// # Example -/// ``` -/// # use iced_web::{slider, Slider}; -/// # -/// pub enum Message { -/// SliderChanged(f32), -/// } -/// -/// let state = &mut slider::State::new(); -/// let value = 50.0; -/// -/// Slider::new(state, 0.0..=100.0, value, Message::SliderChanged); -/// ``` -/// -///  -#[allow(missing_debug_implementations)] -pub struct Slider<'a, T, Message> { - _state: &'a mut State, - range: RangeInclusive<T>, - step: T, - value: T, - on_change: Rc<Box<dyn Fn(T) -> Message>>, - #[allow(dead_code)] - width: Length, - #[allow(dead_code)] - style_sheet: Box<dyn StyleSheet + 'a>, -} - -impl<'a, T, Message> Slider<'a, T, Message> -where - T: Copy + From<u8> + std::cmp::PartialOrd, -{ - /// Creates a new [`Slider`]. - /// - /// It expects: - /// * the local [`State`] of the [`Slider`] - /// * an inclusive range of possible values - /// * the current value of the [`Slider`] - /// * a function that will be called when the [`Slider`] is dragged. - /// It receives the new value of the [`Slider`] and must produce a - /// `Message`. - pub fn new<F>( - state: &'a mut State, - range: RangeInclusive<T>, - value: T, - on_change: F, - ) -> Self - where - F: 'static + Fn(T) -> Message, - { - let value = if value >= *range.start() { - value - } else { - *range.start() - }; - - let value = if value <= *range.end() { - value - } else { - *range.end() - }; - - Slider { - _state: state, - value, - range, - step: T::from(1), - on_change: Rc::new(Box::new(on_change)), - width: Length::Fill, - style_sheet: Default::default(), - } - } - - /// Sets the width of the [`Slider`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the style of the [`Slider`]. - pub fn style( - mut self, - style_sheet: impl Into<Box<dyn StyleSheet + 'a>>, - ) -> Self { - self.style_sheet = style_sheet.into(); - self - } - - /// Sets the step size of the [`Slider`]. - pub fn step(mut self, step: T) -> Self { - self.step = step; - self - } -} - -impl<'a, T, Message> Widget<Message> for Slider<'a, T, Message> -where - T: 'static + Copy + Into<f64> + num_traits::FromPrimitive, - Message: 'static, -{ - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - bus: &Bus<Message>, - _style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - use wasm_bindgen::JsCast; - - let (start, end) = self.range.clone().into_inner(); - - let min = bumpalo::format!(in bump, "{}", start.into()); - let max = bumpalo::format!(in bump, "{}", end.into()); - let value = bumpalo::format!(in bump, "{}", self.value.into()); - let step = bumpalo::format!(in bump, "{}", self.step.into()); - - let on_change = self.on_change.clone(); - let event_bus = bus.clone(); - - // TODO: Styling - input(bump) - .attr("type", "range") - .attr("step", step.into_bump_str()) - .attr("min", min.into_bump_str()) - .attr("max", max.into_bump_str()) - .attr("value", value.into_bump_str()) - .attr("style", "width: 100%") - .on("input", move |_root, _vdom, event| { - let slider = match event.target().and_then(|t| { - t.dyn_into::<web_sys::HtmlInputElement>().ok() - }) { - None => return, - Some(slider) => slider, - }; - - if let Ok(value) = slider.value().parse::<f64>() { - if let Some(value) = T::from_f64(value) { - event_bus.publish(on_change(value)); - } - } - }) - .finish() - } -} - -impl<'a, T, Message> From<Slider<'a, T, Message>> for Element<'a, Message> -where - T: 'static + Copy + Into<f64> + num_traits::FromPrimitive, - Message: 'static, -{ - fn from(slider: Slider<'a, T, Message>) -> Element<'a, Message> { - Element::new(slider) - } -} - -/// The local state of a [`Slider`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct State; - -impl State { - /// Creates a new [`State`]. - pub fn new() -> Self { - Self - } -} diff --git a/web/src/widget/space.rs b/web/src/widget/space.rs deleted file mode 100644 index a8571fdb..00000000 --- a/web/src/widget/space.rs +++ /dev/null @@ -1,63 +0,0 @@ -use crate::{css, Bus, Css, Element, Length, Widget}; -use dodrio::bumpalo; - -/// An amount of empty space. -/// -/// It can be useful if you want to fill some space with nothing. -#[derive(Debug)] -pub struct Space { - width: Length, - height: Length, -} - -impl Space { - /// Creates an amount of empty [`Space`] with the given width and height. - pub fn new(width: Length, height: Length) -> Self { - Space { width, height } - } - - /// Creates an amount of horizontal [`Space`]. - pub fn with_width(width: Length) -> Self { - Space { - width, - height: Length::Shrink, - } - } - - /// Creates an amount of vertical [`Space`]. - pub fn with_height(height: Length) -> Self { - Space { - width: Length::Shrink, - height, - } - } -} - -impl<'a, Message> Widget<Message> for Space { - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - _publish: &Bus<Message>, - _css: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - - let width = css::length(self.width); - let height = css::length(self.height); - - let style = bumpalo::format!( - in bump, - "width: {}; height: {};", - width, - height - ); - - div(bump).attr("style", style.into_bump_str()).finish() - } -} - -impl<'a, Message> From<Space> for Element<'a, Message> { - fn from(space: Space) -> Element<'a, Message> { - Element::new(space) - } -} diff --git a/web/src/widget/text.rs b/web/src/widget/text.rs deleted file mode 100644 index 53d57bfd..00000000 --- a/web/src/widget/text.rs +++ /dev/null @@ -1,148 +0,0 @@ -use crate::alignment; -use crate::css; -use crate::{Bus, Color, Css, Element, Font, Length, Widget}; -use dodrio::bumpalo; - -/// A paragraph of text. -/// -/// # Example -/// -/// ``` -/// # use iced_web::Text; -/// -/// Text::new("I <3 iced!") -/// .size(40); -/// ``` -#[derive(Debug, Clone)] -pub struct Text { - content: String, - size: Option<u16>, - color: Option<Color>, - font: Font, - width: Length, - height: Length, - horizontal_alignment: alignment::Horizontal, - vertical_alignment: alignment::Vertical, -} - -impl Text { - /// Create a new fragment of [`Text`] with the given contents. - pub fn new<T: Into<String>>(label: T) -> Self { - Text { - content: label.into(), - size: None, - color: None, - font: Font::Default, - width: Length::Shrink, - height: Length::Shrink, - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, - } - } - - /// Sets the size of the [`Text`]. - pub fn size(mut self, size: u16) -> Self { - self.size = Some(size); - self - } - - /// Sets the [`Color`] of the [`Text`]. - pub fn color<C: Into<Color>>(mut self, color: C) -> Self { - self.color = Some(color.into()); - self - } - - /// Sets the [`Font`] of the [`Text`]. - pub fn font(mut self, font: Font) -> Self { - self.font = font; - self - } - - /// Sets the width of the [`Text`] boundaries. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Text`] boundaries. - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } - - /// Sets the [`HorizontalAlignment`] of the [`Text`]. - pub fn horizontal_alignment( - mut self, - alignment: alignment::Horizontal, - ) -> Self { - self.horizontal_alignment = alignment; - self - } - - /// Sets the [`VerticalAlignment`] of the [`Text`]. - pub fn vertical_alignment( - mut self, - alignment: alignment::Vertical, - ) -> Self { - self.vertical_alignment = alignment; - self - } -} - -impl<'a, Message> Widget<Message> for Text { - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - _publish: &Bus<Message>, - _style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - - let content = { - use dodrio::bumpalo::collections::String; - - String::from_str_in(&self.content, bump) - }; - - let color = self - .color - .map(css::color) - .unwrap_or(String::from("inherit")); - - let width = css::length(self.width); - let height = css::length(self.height); - - let text_align = match self.horizontal_alignment { - alignment::Horizontal::Left => "left", - alignment::Horizontal::Center => "center", - alignment::Horizontal::Right => "right", - }; - - let style = bumpalo::format!( - in bump, - "width: {}; height: {}; font-size: {}px; color: {}; \ - text-align: {}; font-family: {}", - width, - height, - self.size.unwrap_or(20), - color, - text_align, - match self.font { - Font::Default => "inherit", - Font::External { name, .. } => name, - } - ); - - // TODO: Complete styling - p(bump) - .attr("style", style.into_bump_str()) - .children(vec![text(content.into_bump_str())]) - .finish() - } -} - -impl<'a, Message> From<Text> for Element<'a, Message> { - fn from(text: Text) -> Element<'a, Message> { - Element::new(text) - } -} diff --git a/web/src/widget/text_input.rs b/web/src/widget/text_input.rs deleted file mode 100644 index c5874485..00000000 --- a/web/src/widget/text_input.rs +++ /dev/null @@ -1,234 +0,0 @@ -//! Display fields that can be filled with text. -//! -//! A [`TextInput`] has some local [`State`]. -use crate::{bumpalo, css, Bus, Css, Element, Length, Padding, Widget}; - -pub use iced_style::text_input::{Style, StyleSheet}; - -use std::{rc::Rc, u32}; - -/// A field that can be filled with text. -/// -/// # Example -/// ``` -/// # use iced_web::{text_input, TextInput}; -/// # -/// enum Message { -/// TextInputChanged(String), -/// } -/// -/// let mut state = text_input::State::new(); -/// let value = "Some text"; -/// -/// let input = TextInput::new( -/// &mut state, -/// "This is the placeholder...", -/// value, -/// Message::TextInputChanged, -/// ); -/// ``` -#[allow(missing_debug_implementations)] -pub struct TextInput<'a, Message> { - _state: &'a mut State, - placeholder: String, - value: String, - is_secure: bool, - width: Length, - max_width: u32, - padding: Padding, - size: Option<u16>, - on_change: Rc<Box<dyn Fn(String) -> Message>>, - on_submit: Option<Message>, - style_sheet: Box<dyn StyleSheet + 'a>, -} - -impl<'a, Message> TextInput<'a, Message> { - /// Creates a new [`TextInput`]. - /// - /// It expects: - /// - some [`State`] - /// - a placeholder - /// - the current value - /// - a function that produces a message when the [`TextInput`] changes - pub fn new<F>( - state: &'a mut State, - placeholder: &str, - value: &str, - on_change: F, - ) -> Self - where - F: 'static + Fn(String) -> Message, - { - Self { - _state: state, - placeholder: String::from(placeholder), - value: String::from(value), - is_secure: false, - width: Length::Fill, - max_width: u32::MAX, - padding: Padding::ZERO, - size: None, - on_change: Rc::new(Box::new(on_change)), - on_submit: None, - style_sheet: Default::default(), - } - } - - /// Converts the [`TextInput`] into a secure password input. - pub fn password(mut self) -> Self { - self.is_secure = true; - self - } - - /// Sets the width of the [`TextInput`]. - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the maximum width of the [`TextInput`]. - pub fn max_width(mut self, max_width: u32) -> Self { - self.max_width = max_width; - self - } - - /// Sets the [`Padding`] of the [`TextInput`]. - pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self { - self.padding = padding.into(); - self - } - - /// Sets the text size of the [`TextInput`]. - pub fn size(mut self, size: u16) -> Self { - self.size = Some(size); - self - } - - /// Sets the message that should be produced when the [`TextInput`] is - /// focused and the enter key is pressed. - pub fn on_submit(mut self, message: Message) -> Self { - self.on_submit = Some(message); - self - } - - /// Sets the style of the [`TextInput`]. - pub fn style( - mut self, - style_sheet: impl Into<Box<dyn StyleSheet + 'a>>, - ) -> Self { - self.style_sheet = style_sheet.into(); - self - } -} - -impl<'a, Message> Widget<Message> for TextInput<'a, Message> -where - Message: 'static + Clone, -{ - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - bus: &Bus<Message>, - _style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - use wasm_bindgen::JsCast; - - let placeholder = { - use dodrio::bumpalo::collections::String; - - String::from_str_in(&self.placeholder, bump).into_bump_str() - }; - - let value = { - use dodrio::bumpalo::collections::String; - - String::from_str_in(&self.value, bump).into_bump_str() - }; - - let on_change = self.on_change.clone(); - let on_submit = self.on_submit.clone(); - let input_event_bus = bus.clone(); - let submit_event_bus = bus.clone(); - let style = self.style_sheet.active(); - - input(bump) - .attr( - "style", - bumpalo::format!( - in bump, - "width: {}; max-width: {}; padding: {}; font-size: {}px; \ - background: {}; border-width: {}px; border-color: {}; \ - border-radius: {}px; color: {}", - css::length(self.width), - css::max_length(self.max_width), - css::padding(self.padding), - self.size.unwrap_or(20), - css::background(style.background), - style.border_width, - css::color(style.border_color), - style.border_radius, - css::color(self.style_sheet.value_color()) - ) - .into_bump_str(), - ) - .attr("placeholder", placeholder) - .attr("value", value) - .attr("type", if self.is_secure { "password" } else { "text" }) - .on("input", move |_root, _vdom, event| { - let text_input = match event.target().and_then(|t| { - t.dyn_into::<web_sys::HtmlInputElement>().ok() - }) { - None => return, - Some(text_input) => text_input, - }; - - input_event_bus.publish(on_change(text_input.value())); - }) - .on("keypress", move |_root, _vdom, event| { - if let Some(on_submit) = on_submit.clone() { - let event = - event.unchecked_into::<web_sys::KeyboardEvent>(); - - match event.key_code() { - 13 => { - submit_event_bus.publish(on_submit); - } - _ => {} - } - } - }) - .finish() - } -} - -impl<'a, Message> From<TextInput<'a, Message>> for Element<'a, Message> -where - Message: 'static + Clone, -{ - fn from(text_input: TextInput<'a, Message>) -> Element<'a, Message> { - Element::new(text_input) - } -} - -/// The state of a [`TextInput`]. -#[derive(Debug, Clone, Copy, Default)] -pub struct State; - -impl State { - /// Creates a new [`State`], representing an unfocused [`TextInput`]. - pub fn new() -> Self { - Self::default() - } - - /// Creates a new [`State`], representing a focused [`TextInput`]. - pub fn focused() -> Self { - // TODO - Self::default() - } - - /// Selects all the content of the [`TextInput`]. - pub fn select_all(&mut self) { - // TODO - } -} diff --git a/web/src/widget/toggler.rs b/web/src/widget/toggler.rs deleted file mode 100644 index 0a198079..00000000 --- a/web/src/widget/toggler.rs +++ /dev/null @@ -1,171 +0,0 @@ -//! Show toggle controls using togglers. -use crate::{css, Bus, Css, Element, Length, Widget}; - -pub use iced_style::toggler::{Style, StyleSheet}; - -use dodrio::bumpalo; -use std::rc::Rc; - -/// A toggler that can be toggled. -/// -/// # Example -/// -/// ``` -/// # use iced_web::Toggler; -/// -/// pub enum Message { -/// TogglerToggled(bool), -/// } -/// -/// let is_active = true; -/// -/// Toggler::new(is_active, String::from("Toggle me!"), Message::TogglerToggled); -/// ``` -/// -#[allow(missing_debug_implementations)] -pub struct Toggler<Message> { - is_active: bool, - on_toggle: Rc<dyn Fn(bool) -> Message>, - label: Option<String>, - id: Option<String>, - width: Length, - style: Box<dyn StyleSheet>, -} - -impl<Message> Toggler<Message> { - /// Creates a new [`Toggler`]. - /// - /// It expects: - /// * a boolean describing whether the [`Toggler`] is active or not - /// * An optional label for the [`Toggler`] - /// * a function that will be called when the [`Toggler`] is toggled. It - /// will receive the new state of the [`Toggler`] and must produce a - /// `Message`. - /// - /// [`Toggler`]: struct.Toggler.html - pub fn new<F>( - is_active: bool, - label: impl Into<Option<String>>, - f: F, - ) -> Self - where - F: 'static + Fn(bool) -> Message, - { - Toggler { - is_active, - on_toggle: Rc::new(f), - label: label.into(), - id: None, - width: Length::Shrink, - style: Default::default(), - } - } - - /// Sets the width of the [`Toggler`]. - /// - /// [`Toggler`]: struct.Toggler.html - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the style of the [`Toggler`]. - /// - /// [`Toggler`]: struct.Toggler.html - pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> Self { - self.style = style.into(); - self - } - - /// Sets the id of the [`Toggler`]. - /// - /// [`Toggler`]: struct.Toggler.html - pub fn id(mut self, id: impl Into<String>) -> Self { - self.id = Some(id.into()); - self - } -} - -impl<Message> Widget<Message> for Toggler<Message> -where - Message: 'static, -{ - fn node<'b>( - &self, - bump: &'b bumpalo::Bump, - bus: &Bus<Message>, - style_sheet: &mut Css<'b>, - ) -> dodrio::Node<'b> { - use dodrio::builder::*; - use dodrio::bumpalo::collections::String; - - let toggler_label = &self - .label - .as_ref() - .map(|label| String::from_str_in(&label, bump).into_bump_str()); - - let event_bus = bus.clone(); - let on_toggle = self.on_toggle.clone(); - let is_active = self.is_active; - - let row_class = style_sheet.insert(bump, css::Rule::Row); - let toggler_class = style_sheet.insert(bump, css::Rule::Toggler(16)); - - let (label, input) = if let Some(id) = &self.id { - let id = String::from_str_in(id, bump).into_bump_str(); - - (label(bump).attr("for", id), input(bump).attr("id", id)) - } else { - (label(bump), input(bump)) - }; - - let checkbox = input - .attr("type", "checkbox") - .bool_attr("checked", self.is_active) - .on("click", move |_root, vdom, _event| { - let msg = on_toggle(!is_active); - event_bus.publish(msg); - - vdom.schedule_render(); - }) - .finish(); - - let toggler = span(bump).children(vec![span(bump).finish()]).finish(); - - label - .attr( - "class", - bumpalo::format!(in bump, "{} {}", row_class, toggler_class) - .into_bump_str(), - ) - .attr( - "style", - bumpalo::format!(in bump, "width: {}; align-items: center", css::length(self.width)) - .into_bump_str() - ) - .children( - if let Some(label) = toggler_label { - vec![ - text(label), - checkbox, - toggler, - ] - } else { - vec![ - checkbox, - toggler, - ] - } - ) - .finish() - } -} - -impl<'a, Message> From<Toggler<Message>> for Element<'a, Message> -where - Message: 'static, -{ - fn from(toggler: Toggler<Message>) -> Element<'a, Message> { - Element::new(toggler) - } -} diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index f4c4fa2c..b4173413 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -2,7 +2,7 @@ name = "iced_wgpu" version = "0.4.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" description = "A wgpu renderer for Iced" license = "MIT AND OFL-1.1" repository = "https://github.com/iced-rs/iced" @@ -25,6 +25,7 @@ canvas = ["iced_graphics/canvas"] qr_code = ["iced_graphics/qr_code"] default_system_font = ["iced_graphics/font-source"] spirv = ["wgpu/spirv"] +webgl = ["wgpu/webgl"] [dependencies] wgpu = "0.12" diff --git a/wgpu/README.md b/wgpu/README.md index eda0ba46..50440baf 100644 --- a/wgpu/README.md +++ b/wgpu/README.md @@ -2,7 +2,7 @@ [][documentation] [](https://crates.io/crates/iced_wgpu) [](https://github.com/iced-rs/iced/blob/master/LICENSE) -[](https://iced.zulipchat.com) +[](https://discord.gg/3xZJ65GAhd) `iced_wgpu` is a [`wgpu`] renderer for [`iced_native`]. For now, it is the default renderer of Iced in native platforms. @@ -21,7 +21,7 @@ Currently, `iced_wgpu` supports the following primitives: [documentation]: https://docs.rs/iced_wgpu [`iced_native`]: ../native -[`wgpu`]: https://github.com/gfx-rs/wgpu-rs +[`wgpu`]: https://github.com/gfx-rs/wgpu [WebGPU API]: https://gpuweb.github.io/gpuweb/ [`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index e13987b4..ec5e911f 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -163,8 +163,6 @@ impl Operation { where R: std::io::BufRead + std::io::Seek, { - use std::convert::TryFrom; - let exif = exif::Reader::new().read_from_container(reader)?; Ok(exif diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 336696ee..45f1f2de 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -48,11 +48,15 @@ impl Pipeline { .expect("Load fallback font") }); - let draw_brush = + let draw_brush_builder = wgpu_glyph::GlyphBrushBuilder::using_font(font.clone()) .initial_cache_size((2048, 2048)) - .draw_cache_multithread(multithreading) - .build(device, format); + .draw_cache_multithread(multithreading); + + #[cfg(target_arch = "wasm32")] + let draw_brush_builder = draw_brush_builder.draw_cache_align_4x4(true); + + let draw_brush = draw_brush_builder.build(device, format); let measure_brush = glyph_brush::GlyphBrushBuilder::using_font(font).build(); diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 3b264475..6feb795b 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -48,6 +48,13 @@ impl Compositor { .as_ref() .and_then(|surface| surface.get_preferred_format(&adapter))?; + #[cfg(target_arch = "wasm32")] + let limits = wgpu::Limits::downlevel_webgl2_defaults() + .using_resolution(adapter.limits()); + + #[cfg(not(target_arch = "wasm32"))] + let limits = wgpu::Limits::default(); + let (device, queue) = adapter .request_device( &wgpu::DeviceDescriptor { @@ -57,7 +64,7 @@ impl Compositor { features: wgpu::Features::empty(), limits: wgpu::Limits { max_bind_groups: 2, - ..wgpu::Limits::default() + ..limits }, }, None, diff --git a/winit/Cargo.toml b/winit/Cargo.toml index bfcfacbc..f7232248 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -2,7 +2,7 @@ name = "iced_winit" version = "0.3.0" authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2018" +edition = "2021" description = "A winit runtime for Iced" license = "MIT" repository = "https://github.com/hecrj/iced" @@ -37,3 +37,7 @@ path = "../futures" [target.'cfg(target_os = "windows")'.dependencies.winapi] version = "0.3.6" + +[target.'cfg(target_arch = "wasm32")'.dependencies.web-sys] +version = "0.3" +features = ["Document", "Window"] diff --git a/winit/README.md b/winit/README.md index 58c782c6..5a94cd92 100644 --- a/winit/README.md +++ b/winit/README.md @@ -2,7 +2,7 @@ [][documentation] [](https://crates.io/crates/iced_winit) [](https://github.com/hecrj/iced/blob/master/LICENSE) -[](https://iced.zulipchat.com) +[](https://discord.gg/3xZJ65GAhd) `iced_winit` offers some convenient abstractions on top of [`iced_native`] to quickstart development when using [`winit`]. @@ -26,4 +26,4 @@ iced_winit = "0.3" __Iced moves fast and the `master` branch can contain breaking changes!__ If you want to learn about a specific release, check out [the release list]. -[the release list]: https://github.com/hecrj/iced/releases +[the release list]: https://github.com/iced-rs/iced/releases diff --git a/winit/src/application.rs b/winit/src/application.rs index e9212b5e..ed077507 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -115,12 +115,11 @@ where use futures::task; use futures::Future; use winit::event_loop::EventLoop; - use winit::platform::run_return::EventLoopExtRunReturn; let mut debug = Debug::new(); debug.startup_started(); - let mut event_loop = EventLoop::with_user_event(); + let event_loop = EventLoop::with_user_event(); let mut proxy = event_loop.create_proxy(); let mut runtime = { @@ -149,6 +148,21 @@ where .build(&event_loop) .map_err(Error::WindowCreationFailed)?; + #[cfg(target_arch = "wasm32")] + { + use winit::platform::web::WindowExtWebSys; + + let canvas = window.canvas(); + + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + let body = document.body().unwrap(); + + let _ = body + .append_child(&canvas) + .expect("Append canvas to HTML body"); + } + let mut clipboard = Clipboard::connect(&window); run_command( @@ -179,7 +193,7 @@ where let mut context = task::Context::from_waker(task::noop_waker_ref()); - event_loop.run_return(move |event, _, control_flow| { + platform::run(event_loop, move |event, _, control_flow| { use winit::event_loop::ControlFlow; if let ControlFlow::Exit = control_flow { @@ -211,9 +225,7 @@ where task::Poll::Ready(_) => ControlFlow::Exit, }; } - }); - - Ok(()) + }) } async fn run_instance<A, E, C>( @@ -562,3 +574,43 @@ pub fn run_command<Message: 'static + std::fmt::Debug + Send, E: Executor>( } } } + +#[cfg(not(target_arch = "wasm32"))] +mod platform { + pub fn run<T, F>( + mut event_loop: winit::event_loop::EventLoop<T>, + event_handler: F, + ) -> Result<(), super::Error> + where + F: 'static + + FnMut( + winit::event::Event<'_, T>, + &winit::event_loop::EventLoopWindowTarget<T>, + &mut winit::event_loop::ControlFlow, + ), + { + use winit::platform::run_return::EventLoopExtRunReturn; + + let _ = event_loop.run_return(event_handler); + + Ok(()) + } +} + +#[cfg(target_arch = "wasm32")] +mod platform { + pub fn run<T, F>( + event_loop: winit::event_loop::EventLoop<T>, + event_handler: F, + ) -> ! + where + F: 'static + + FnMut( + winit::event::Event<'_, T>, + &winit::event_loop::EventLoopWindowTarget<T>, + &mut winit::event_loop::ControlFlow, + ), + { + event_loop.run(event_handler) + } +} diff --git a/winit/src/clipboard.rs b/winit/src/clipboard.rs index 1b92b28d..c1fd8813 100644 --- a/winit/src/clipboard.rs +++ b/winit/src/clipboard.rs @@ -26,6 +26,14 @@ impl Clipboard { Clipboard { state } } + /// Creates a new [`Clipboard`] that isn't associated with a window. + /// This clipboard will never contain a copied value. + pub fn unconnected() -> Clipboard { + Clipboard { + state: State::Unavailable, + } + } + /// Reads the current content of the [`Clipboard`] as text. pub fn read(&self) -> Option<String> { match &self.state { diff --git a/winit/src/settings.rs b/winit/src/settings.rs index 045cb156..9a93824a 100644 --- a/winit/src/settings.rs +++ b/winit/src/settings.rs @@ -38,6 +38,12 @@ pub struct Settings<Flags> { /// Whether the [`Application`] should exit when the user requests the /// window to close (e.g. the user presses the close button). pub exit_on_close_request: bool, + + /// Whether the [`Application`] should try to build the context + /// using OpenGL ES first then OpenGL. + /// + /// NOTE: Only works for the `glow` backend. + pub try_opengles_first: bool, } /// The window settings of an application. |