add relm4 and target build
This commit is contained in:
parent
5aa6ab9fae
commit
4418ee0e4b
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -1154,8 +1154,6 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "libadwaita"
|
name = "libadwaita"
|
||||||
version = "0.4.4"
|
version = "0.4.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1ab9c0843f9f23ff25634df2743690c3a1faffe0a190e60c490878517eb81abf"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
"gdk-pixbuf",
|
"gdk-pixbuf",
|
||||||
@ -1626,8 +1624,6 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "relm4"
|
name = "relm4"
|
||||||
version = "0.6.2"
|
version = "0.6.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0c16f3fad883034773b7f5af4d7e865532b8f3641e5a8bab2a34561a8d960d81"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"flume",
|
"flume",
|
||||||
@ -1654,8 +1650,6 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "relm4-macros"
|
name = "relm4-macros"
|
||||||
version = "0.6.2"
|
version = "0.6.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9340e2553c0a184a80a0bfa1dcf73c47f3d48933aa6be90724b202f9fbd24735"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|||||||
@ -7,8 +7,9 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gtk = { version = "0.6.6", package = "gtk4", features = ["v4_8"] }
|
gtk = { version = "0.6.6", package = "gtk4", features = ["v4_8"] }
|
||||||
adw = { version = "0.4.3", package = "libadwaita", features = ["v1_4"]}
|
libadwaita = {package="libadwaita", version = "0.4.4", path="./libadwaita-0.4.4", features = ["v1_4"]}
|
||||||
relm4 = { version = "0.6.2", features = ["libadwaita"]}
|
# relm4 = { version = "0.6.2", features = ["libadwaita"]}
|
||||||
|
relm4 = {package = "relm4", version="0.6.2", path="./Relm4-0.6.2/relm4", features=["libadwaita"]}
|
||||||
relm4-icons = { version = "0.6.0", features = ["add-filled"] }
|
relm4-icons = { version = "0.6.0", features = ["add-filled"] }
|
||||||
chrono = "0.4.34"
|
chrono = "0.4.34"
|
||||||
tracker = "0.2.1"
|
tracker = "0.2.1"
|
||||||
|
|||||||
558
Relm4-0.6.2/CHANGES.md
Normal file
558
Relm4-0.6.2/CHANGES.md
Normal file
@ -0,0 +1,558 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## 0.6.2 - 2023-9-27
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
+ macros: Add support for accessing nested template children
|
||||||
|
|
||||||
|
## 0.6.1 - 2023-8-9
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
+ core: Add `TypedColumnView` as a typed wrapper for `gtk::ColumnView`
|
||||||
|
+ core: Add `set_margin_vertical` and `set_margin_horizontal` to RelmWidgetExt
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
+ core: Don't panic when dropping components from asynchronous contexts
|
||||||
|
+ core: Fix an issue with using `connect_open` on `gtk::Application`
|
||||||
|
+ core: Use GNOME 42 as baseline feature to help with Ubuntu 22.04
|
||||||
|
+ core: Fix compiler error when not using the "macros" feature
|
||||||
|
+ macros: Allow trailing commas in view!
|
||||||
|
+ macros: Allow multiple instances of the same template children
|
||||||
|
+ components: Disable default features of relm4
|
||||||
|
+ examples: Fix libadwaita tab examples
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
+ examples: Add a separator to the libadwaita leaflet example.
|
||||||
|
|
||||||
|
## 0.6.0 - 2023-5-31
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
+ core: Implemented `RelmRemoveExt` for `adw::ExpanderRow`.
|
||||||
|
+ core: Implemented `ContainerChild` for `adw::ExpanderRow`.
|
||||||
|
+ core: Add `TypedListView` as idiomatic wrapper over `gtk::ListView`
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
+ macros: Improve error messages for non-identifier parameter patterns
|
||||||
|
|
||||||
|
## 0.6.0-beta.1 - 2023-4-19
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
+ core: Introduce setting and action safeties
|
||||||
|
+ core: Implement `RelmSetChildExt` for `gtk::AspectFrame`
|
||||||
|
+ core: Add `FactoryHashMap` as alternative to `FactoryVecDeque`
|
||||||
|
+ core: Add gnome_44 feature flag for GNOME 44
|
||||||
|
+ core: Documentation and better support for data bindings
|
||||||
|
+ core: Add `set_tooltip` method to `RelmWidgetExt`
|
||||||
|
+ core: Add `main_adw_application` method to retrieve the `adw::Application` when the libadwaita feature is enabled
|
||||||
|
+ macros: Add `skip_init` option for watch and track attributes to skip their initialization
|
||||||
|
+ examples: Introduce setting and action safeties
|
||||||
|
+ examples: Example for using relm4-icons
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
+ core: Replace `FactoryVecDeque`'s associated function `from_vec` with `from_iter`
|
||||||
|
+ core: Added `Index` type to the `FactoryComponent` trait
|
||||||
|
+ core: Rename factory component traits `output_to_parent_input` method to `forward_to_parent`
|
||||||
|
+ core: Improved `RelmActionGroup` API
|
||||||
|
+ all: Increase MSRV to 1.65 to match the dependencies
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
+ all: Fix doc links
|
||||||
|
+ core: Improve worker docs
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
+ core: Remove the deprecated `send!` macro
|
||||||
|
|
||||||
|
## 0.6.0-alpha.2 - 2023-3-13
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
+ core: Add data bindings
|
||||||
|
+ core: Implement `FactoryView` for `adw::Leaflet`
|
||||||
|
+ components: WebImage as component for easily loading images from the web
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
+ macros: Support template children of templates that are root widgets
|
||||||
|
+ macros: Fix order of assignment and connect statements
|
||||||
|
|
||||||
|
## 0.6.0-alpha.1 - 2023-2-29
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
+ core: Add `RelmApp` builder methods `with_args` and `with_broker`
|
||||||
|
+ core: Add `MessageBroker` support for `AsyncComponent`
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
+ core: Change `MessageBroker` generic type parameter to message type
|
||||||
|
+ core: Rename `RelmApp` method `with_app` to `from_app`
|
||||||
|
+ core: Deprecate `RelmApp` methods `run_with_args` and `run_async_with_args`
|
||||||
|
|
||||||
|
## 0.5.0 - 2023-2-17
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
+ core: Implement `RelmSetChildExt` for `gtk::Expander`
|
||||||
|
+ macros: Support submenus in menu! macro
|
||||||
|
+ macros: Support widget templates as root widgets of components and factories
|
||||||
|
+ macros: Implement `Clone` for widget templates
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
+ all: Use docs.rs to host the documentation. The documentation on the website will be deprecated.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
+ core: Call shutdown on components even on application shutdown
|
||||||
|
+ core: Clearing and dropping a factory properly calls the shutdown method of its elements
|
||||||
|
+ components: Fix doc links to examples on GitHub
|
||||||
|
+ macros: Fix panic on incorrect root type syntax
|
||||||
|
+ macros: Fix incorrect type generation for generics
|
||||||
|
+ macros: Allow mutably root widgets
|
||||||
|
|
||||||
|
## 0.5.0-rc.2 - 2023-2-5
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
+ core: Increase MSRV to 1.64 to match the gtk4 crate
|
||||||
|
+ all: Move examples into the corresponding crates
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
+ core: Add `broadcast` to `FactoryVecDeque`
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
+ core: Don't crash when the application is started multiple times
|
||||||
|
+ core: Support tokio also on local futures
|
||||||
|
+ core: Prevent leaking `CommandSenderInner` struct
|
||||||
|
+ core: Improve error message when sending input messages to dropped components
|
||||||
|
+ core: Fix scaping of examples on docs.rs
|
||||||
|
+ core: Fix crash caused by UID overflow with very large or frequently changing factories
|
||||||
|
+ macros: Fix clippy warning triggered by the view macro in some edge cases
|
||||||
|
+ macros: Import `relm4::ComponentSender` isn’t longer required
|
||||||
|
|
||||||
|
## 0.5.0-rc.1 - 2022-12-21
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
+ core: Add `try_read` and `try_write` methods to `SharedState`
|
||||||
|
+ core: Allow initializing `FactoryVecDeque` from a `Vec` and make it cloneable
|
||||||
|
+ core: Support factories with `adw::PreferencePage`
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
+ core: Pass `&self` to the `Position::position()` function for positioning widgets
|
||||||
|
+ core: Take `&str` instead of `&[u8]` in `set_global_css()`
|
||||||
|
+ macros: Allow expressions for names of menu entries
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
+ core: Initialize GTK when calling `RelmApp::new()`
|
||||||
|
|
||||||
|
## 0.5.0-beta.5 - 2022-11-26
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
+ core: Add asynchronous components including macro support
|
||||||
|
+ core: Add asynchronous factories including macro support
|
||||||
|
+ core: Temporary widget initialization for async components and factories
|
||||||
|
+ core: Add `LoadingWidgets` to help with temporary loading widgets in async factories and components
|
||||||
|
+ core: Add `Reducer` as message based alternative to `SharedState`
|
||||||
|
+ core: Synchronous API for commands
|
||||||
|
+ core: Remove async-broadcast dependency
|
||||||
|
+ core: Runtimes of `Component`s and `AsyncComponents` can now be detached for a static lifetime
|
||||||
|
+ core: Add `ComponentStream` as alternative to `Controller` that implements `Stream` for async message handling
|
||||||
|
+ core: Add `gnome_42` and `gnome_43` feature flags
|
||||||
|
+ core: Implement `RelmContainerExt` for `adw::Squeezer`
|
||||||
|
+ core: Implement `RelmSetChildExt` for `gtk::WindowHandle`
|
||||||
|
+ macros: Auto-generate the name of the `Widgets` type if possible
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
+ core: Rename `FactoryComponentSender` to `FactorySender` and `AsyncFactoryComponentSender` to `AsyncFactorySender`
|
||||||
|
+ core: The sender API now supports proper error handling
|
||||||
|
+ core: Pass `Root` during `update` and `update_cmd` for `Component` and `AsyncComponent`
|
||||||
|
+ core: Rename `OnDestroy` to `RelmObjectExt`
|
||||||
|
+ core: Remove `EmptyRoot` in favor of the unit type
|
||||||
|
+ macros: Allow using methods calls as widget initializers in the `view` macro
|
||||||
|
+ macros: Explicitly using `visibility` as attribute name is no longer supported
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
+ all: Fix doc builds on docs.rs and add a CI job to prevent future doc failures
|
||||||
|
+ core: Fix various bugs related to component shutdown
|
||||||
|
+ core: `shutdown` on `Component` now works as expected
|
||||||
|
+ core: `shutdown` on `FactoryComponent` now works as expected
|
||||||
|
+ core: `transient_for` on `ComponentBuilder` now works properly when called after the application has been initialized
|
||||||
|
+ macros: Mark template children of public widget templates as public as well
|
||||||
|
+ macros: Only get template children in scope when they are actually used
|
||||||
|
+ macros: Fix type parsing after arrow operator in widget assignments
|
||||||
|
|
||||||
|
## 0.5.0-beta.4 - 2022-10-24
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
+ core: Added `dox` feature to be able to build the docs without the dependencies
|
||||||
|
+ core: Added widget templates
|
||||||
|
+ core: Allow changing the priority of event loops of components
|
||||||
|
+ core: Impl `ContainerChild` and `RelmSetChildExt` for `adw::ToastOverlay`
|
||||||
|
+ components: Added `dox` feature to be able to build the docs without the dependencies
|
||||||
|
+ examples: Add libadwaita Leaflet sidebar example
|
||||||
|
+ examples: Port entry, actions and popover examples to 0.5
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
+ core: Improved `DrawHandler`
|
||||||
|
+ core: Made the `macros` feature a default feature
|
||||||
|
+ core: Remove async-oneshot dependency and replace it with tokio's oneshot channel
|
||||||
|
+ core: Remove WidgetPlus in favor of RelmWidgetExt
|
||||||
|
+ core: Add convenience getter-methods to Controller
|
||||||
|
+ core: `add_action` of `RelmActionGroup` now takes a reference to a `RelmAction` as a parameter
|
||||||
|
+ examples: Many improvements
|
||||||
|
+ macros: `parse_with_path`, `update_stream`, `inject_view_code` and `generate_tokens` take references for some of their parameters
|
||||||
|
+ artwork: Update logo
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
+ macros: Fix usage of RelmContainerExt with local_ref attribute
|
||||||
|
+ macros: Report RelmContainerExt error at the correct span
|
||||||
|
|
||||||
|
## 0.5.0-beta.3 - 2022-9-28
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
+ core: Add `iter_mut` to `FactoryVecDeque`
|
||||||
|
+ core: Impl extension traits and `FactoryView` for `adw::PreferencesGroup`
|
||||||
|
+ core: Add a `prelude` module that contains commonly imported traits and types
|
||||||
|
+ core: Implement RelmContainerExt for Leaflet, Carousel and TabView
|
||||||
|
+ core: Add `iter()` method to `FactoryVecDeque`
|
||||||
|
+ core: Add getter for global application to simplify graceful shutdown of applications
|
||||||
|
+ core: Add MessageBroker type to allow communication between components on different levels
|
||||||
|
+ core: Return a clone of the `DynamicIndex` after inserting into a factory
|
||||||
|
+ macros: Add shorthand syntax for simple input messages
|
||||||
|
+ macros: Add chain attribute for properties
|
||||||
|
+ components: Add `SimpleComboBox` type as a more idiomatic wrapper around `gtk::ComboBoxText`
|
||||||
|
+ components: Port `OpenButton` to 0.5
|
||||||
|
+ book: Many chapters ported to 0.5
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
+ core: Improve `SharedState` interface and prefer method names related to `RwLock`
|
||||||
|
+ core: Remove Debug requirement for FactoryComponent
|
||||||
|
+ core: Remove `input` and `output` fields on `ComponentSender` and `FactoryComponentSender` in favor of `input_sender` and `output_sender` methods
|
||||||
|
+ core: Make `ComponentSender` and `FactoryComponentSender` structs instead of type aliases
|
||||||
|
+ core: Increase MSRV to 1.63 to match the gtk4 crate
|
||||||
|
+ core: Rename `ParentMsg` and `output_to_parent_msg` to `ParentInput` and `output_to_parent_input`, respectively.
|
||||||
|
+ core: Do not call `gtk_init` and `adw_init` in favor of the application startup handler
|
||||||
|
+ core: Remove `Application` type alias in favor of `gtk::Application`
|
||||||
|
+ core: Make `app` field on `RelmApp` private
|
||||||
|
+ core: Use late initialization for transient_for and its native variant
|
||||||
|
+ core: Rename InitParams to Init in SimpleComponent and Worker too
|
||||||
|
+ macros: Don't generate dead code in the widgets struct
|
||||||
|
+ macros: Improve error reporting on invalid trait implementations
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
+ core: Append children for `gtk::Dialog` to its content area instead of using `set_child`
|
||||||
|
+ macros: Fix returned widgets assignment in the view macro
|
||||||
|
|
||||||
|
### Misc
|
||||||
|
|
||||||
|
+ all: Use more clippy lints and clean up the code in general
|
||||||
|
|
||||||
|
## 0.5.0-beta.2 - 2022-8-12
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
+ core: Add oneshot_command method to ComponentSender
|
||||||
|
+ core: Implement FactoryView for adw::Carousel
|
||||||
|
+ components: Complete port to 0.5
|
||||||
|
+ examples: More examples ported to 0.5
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
+ core: Rename InitParams to Init
|
||||||
|
+ core: Pass senders by value
|
||||||
|
+ core: Make factories use FactoryComponentSender instead of individual senders for input and output
|
||||||
|
+ core: Remove generics from FactoryComponent
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
+ macros: Fix unsoundness with thread local memory
|
||||||
|
|
||||||
|
## 0.5.0-beta.1 - 2022-7-26
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
+ core: Introduce commands
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
+ core: The Component trait replaces AppUpdate, ComponentUpdate, AsyncComponentUpdate, MessageHandler, MicroModel, MicroWidgets, Components and Widgets
|
||||||
|
+ core: Replace FactoryPrototype with FactoryComponent
|
||||||
|
+ core: Drop FactoryVec and make FactoryVecDeque easier to use
|
||||||
|
+ core: Improved component initialization and lifecycle
|
||||||
|
+ macros: Replace iterate, track and watch with attributes
|
||||||
|
+ macros: Replace args! with only parenthesis
|
||||||
|
+ macros: Improved macro syntax
|
||||||
|
+ examples: Many rewrites for the new version
|
||||||
|
|
||||||
|
## 0.4.4 - 2022-3-30
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
+ all: Repositories were transferred to the Relm4 organization
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
+ macros: Don't interpret expr != expr as macro
|
||||||
|
+ core: Always initialize GTK/Libadwaita before running apps
|
||||||
|
+ macros: Some doc link fixes
|
||||||
|
|
||||||
|
## 0.4.3 - 2022-3-13
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
+ core: Add WidgetRef trait to make AsRef easier accessible for widgets
|
||||||
|
+ macros: Destructure widgets in Widgets::view
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
+ macros: Use correct widget type in derive macro for components
|
||||||
|
+ macros: Fix parsing of `property: value == other,` expressions
|
||||||
|
+ core: Fixed the position type for TabView
|
||||||
|
+ core: Fixed state changes in FactoryVec (by [V02460](https://github.com/V02460))
|
||||||
|
+ macros: Parse whole expressions instead of just literals
|
||||||
|
|
||||||
|
## 0.4.2 - 2022-2-4
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
+ macros: The view macro now allows dereferencing widgets with *
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
+ core: Fixed clear method of FactoryVec
|
||||||
|
+ macros: The micro_component macro now parses post_view correctly
|
||||||
|
+ macros: Fix the ordering of properties in the view macro
|
||||||
|
+ macros: Fix the ordering of widget assignments in the view macro
|
||||||
|
|
||||||
|
## 0.4.1 - 2022-1-17
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
+ macros: Improved documentation
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
+ core: Action macros now include the required traits themselves
|
||||||
|
+ macros: Allow connecting events in the view macro
|
||||||
|
|
||||||
|
## 0.4.0 - 2022-1-16
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
+ all: Update gtk4-rs to v0.4
|
||||||
|
+ core: Introduce the "macro" feature as alternative to using relm4-macros separately
|
||||||
|
+ macros: Add a macros for MicroComponents and Factories
|
||||||
|
+ macros: Add a post_view function to execute code after the view code of the macro
|
||||||
|
+ macros: Allow using the view and menu macros independently from the widget macro
|
||||||
|
+ macros: Allow using mutable widgets in view
|
||||||
|
+ macros: Improve error messages for anonymous widgets
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
+ core: Renamed methods of the FactoryPrototype trait to better match with the rest of Relm4
|
||||||
|
+ macros: manual_view is now called pre_view
|
||||||
|
+ book: Reworked introduction and first chapter
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
+ core: Fix panic caused by the clear method of FactoryVecDeque
|
||||||
|
|
||||||
|
## 0.4.0-beta.3 - 2021-12-28
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
+ core: A factory view implementation for libadwaita's StackView
|
||||||
|
+ macros: Allow early returns in manual_view (by [euclio](https://github.com/euclio))
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
+ core: Make GTK's command line argument handling optional (by [euclio](https://github.com/euclio))
|
||||||
|
+ core: DynamicIndex now implements Send but panics when used on other threads
|
||||||
|
|
||||||
|
## 0.4.0-beta.2 - 2021-11-26
|
||||||
|
|
||||||
|
+ macros: Add optional returned widget syntax
|
||||||
|
|
||||||
|
## 0.4.0-beta.1 - 2021-11-21
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
+ core: Micro components
|
||||||
|
+ core: Type safe actions API
|
||||||
|
+ macros: Menu macro for creating menus
|
||||||
|
+ macros: New returned widget syntax
|
||||||
|
+ examples Micro components example
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
+ core: Initialize widgets from the outermost components to the app
|
||||||
|
+ macros: component! removed and parent! was added instead
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
+ core: RelmComponent::with_new_thread
|
||||||
|
|
||||||
|
## 0.2.1 - 2021-10-17
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
+ core: Added sender method to RelmComponent
|
||||||
|
+ macros: New shorthand tracker syntax
|
||||||
|
+ macros: Allow generic function parameters in properties
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
+ core: Use adw::Application when "libadwaita" feature is active
|
||||||
|
|
||||||
|
## 0.2.0 - 2021-10-09
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
+ core: Pass model in connect_components function of the Widgets trait
|
||||||
|
+ core: Mini rework of factories
|
||||||
|
+ core: Removed DefaultWidgets trait in favor of Default implementations in gkt4-rs
|
||||||
|
+ book: Many book improvements by [tronta](https://github.com/tronta)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
+ core: Added with_app method that allows passing an existing gtk::Application to Relm4
|
||||||
|
+ core: Methods to access the widgets of components
|
||||||
|
+ core: Re-export for gtk
|
||||||
|
+ macros: Support named arguments in the widget macro (by [mskorkowski](https://github.com/mskorkowski))
|
||||||
|
+ macros: Support usage of re-export paths in the widget macro (by [mskorkowski](https://github.com/mskorkowski))
|
||||||
|
+ macros: Added error message when confusing `=` and `:`
|
||||||
|
+ macros: Allow usage of visibilities other than pub
|
||||||
|
+ macros: New pre_connect_components and post_connect_components for manual components code
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
+ macros: Parsing the first widget should now always work as expected
|
||||||
|
+ macros: [#20](https://github.com/Relm4/relm4/issues/20) Fix wrong order when using components in the widget macro
|
||||||
|
|
||||||
|
## 0.1.0 - 2021-09-06
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
+ core: Added message handler type
|
||||||
|
+ core: More methods for factory data structures
|
||||||
|
+ macros: Add syntax for connecting events with components
|
||||||
|
+ examples: Stack example
|
||||||
|
+ book: Added macro expansion chapter
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
+ book: Added message handler chapter and reworked the threads and async chapter
|
||||||
|
+ book: Many book improvements by [tronta](https://github.com/tronta)
|
||||||
|
+ core: The send! macro no longer clones the sender
|
||||||
|
+ macros: Make fields of public widgets public
|
||||||
|
+ components: Use &'static str instead of String for configurations
|
||||||
|
+ examples: Many improvements
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
+ macros: Use fully qualified syntax for factories
|
||||||
|
+ macros: Passing additional arguments now works for components and other properties, too.
|
||||||
|
|
||||||
|
## 0.1.0-beta.9 - 2021-08-24
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
+ components: Open button with automatic recent files list
|
||||||
|
+ components: Removed trait duplication and added more docs
|
||||||
|
+ core: Iterators added to factory data structures
|
||||||
|
+ core: More widgets added as FactoryView
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
+ book: Many book improvements by [tronta](https://github.com/tronta)
|
||||||
|
+ core: Removed class name methods from WidgetPlus [#14](https://github.com/Relm4/relm4/pull/14)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
+ macros: Parsing additional fields should be more stable now
|
||||||
|
+ macros: Widgets can not include comments at the top
|
||||||
|
|
||||||
|
## 0.1.0-beta.8 - 2021-08-20
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
+ core: Support for libadwaita 🎉
|
||||||
|
+ macros: Fully qualified syntax for trait disambiguation
|
||||||
|
+ macros: Allow passing additional arguments to widget initialization (useful e.g. for grids)
|
||||||
|
+ book: Reusable components and widget macro reference chapters
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
+ macros: Improved error messages
|
||||||
|
|
||||||
|
## 0.1.0-beta.7 - 2021-08-19
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
+ book: Factory, components, worker and thread + async chapters
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
+ core: get and get_mut of FactoryVec and FactoryVecDeque now return an Option to prevent panics
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
+ macros: Fixed components
|
||||||
|
+ core: Fixed unsound removal of elements in FactoryVecDeque
|
||||||
|
|
||||||
|
|
||||||
|
## 0.1.0-beta.6 - 2021-08-18
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
+ core: Improved and adjusted the FactoryPrototype trait
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
+ core: Added the FactoryListView trait for more flexibility
|
||||||
|
+ core: Added a FactoryVecDeque container
|
||||||
|
+ core: Implemented FactoryView and FactoryListView for more widgets
|
||||||
|
+ examples: More examples
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
+ macros: Fixed the factory! macro
|
||||||
|
|
||||||
|
## 0.1.0-beta.5 - 2021-08-15
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
+ core: Drawing handler for gtk::DrawingArea
|
||||||
|
+ core: New CSS methods in WidgetPlus trait
|
||||||
|
+ examples: Many new examples
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
+ core: Many doc improvements
|
||||||
|
+ macros: Improved tracker! macro
|
||||||
26
Relm4-0.6.2/Cargo.toml
Normal file
26
Relm4-0.6.2/Cargo.toml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
[workspace]
|
||||||
|
resolver = "2"
|
||||||
|
members = [
|
||||||
|
"relm4",
|
||||||
|
"relm4-components",
|
||||||
|
"relm4-macros",
|
||||||
|
]
|
||||||
|
exclude = [
|
||||||
|
"examples",
|
||||||
|
"examples/libadwaita",
|
||||||
|
]
|
||||||
|
|
||||||
|
[workspace.package]
|
||||||
|
version = "0.6.2"
|
||||||
|
authors = ["Aaron Erhardt <aaron.erhardt@t-online.de>"]
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.65"
|
||||||
|
readme = "README.md"
|
||||||
|
license = "Apache-2.0 OR MIT"
|
||||||
|
description = "An idiomatic GUI library inspired by Elm and based on gtk4-rs"
|
||||||
|
|
||||||
|
homepage = "https://relm4.org"
|
||||||
|
repository = "https://github.com/Relm4/Relm4"
|
||||||
|
|
||||||
|
keywords = ["gui", "gtk", "gtk4", "elm"]
|
||||||
|
categories = ["gui"]
|
||||||
176
Relm4-0.6.2/LICENSE-APACHE
Normal file
176
Relm4-0.6.2/LICENSE-APACHE
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
23
Relm4-0.6.2/LICENSE-MIT
Normal file
23
Relm4-0.6.2/LICENSE-MIT
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
Permission is hereby granted, free of charge, to any
|
||||||
|
person obtaining a copy of this software and associated
|
||||||
|
documentation files (the "Software"), to deal in the
|
||||||
|
Software without restriction, including without
|
||||||
|
limitation the rights to use, copy, modify, merge,
|
||||||
|
publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software
|
||||||
|
is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice
|
||||||
|
shall be included in all copies or substantial portions
|
||||||
|
of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||||
|
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||||
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||||
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
191
Relm4-0.6.2/README.md
Normal file
191
Relm4-0.6.2/README.md
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
<h1>
|
||||||
|
<a href="https://relm4.org">
|
||||||
|
<img src="assets/Relm_logo_with_text.png" width="200" alt="Relm4">
|
||||||
|
</a>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
[](https://github.com/Relm4/Relm4/actions/workflows/rust.yml)
|
||||||
|
[](https://matrix.to/#/#relm4:matrix.org)
|
||||||
|
[](https://crates.io/crates/relm4)
|
||||||
|
[](https://docs.rs/relm4/)
|
||||||
|
[](https://relm4.org/book/stable/)
|
||||||
|

|
||||||
|
[](https://deps.rs/repo/github/Relm4/Relm4)
|
||||||
|
|
||||||
|
An idiomatic GUI library inspired by [Elm](https://elm-lang.org/) and based on [gtk4-rs](https://crates.io/crates/gtk4).
|
||||||
|
Relm4 is a new version of [relm](https://github.com/antoyo/relm) that's built from scratch and is compatible with [GTK4](https://www.gtk.org/) and [libadwaita](https://gitlab.gnome.org/GNOME/libadwaita).
|
||||||
|
|
||||||
|
## Why Relm4
|
||||||
|
|
||||||
|
We believe that GUI development should be easy, productive and delightful.
|
||||||
|
The [gtk4-rs](https://crates.io/crates/gtk4) crate already provides everything you need to write modern, beautiful and cross-platform applications.
|
||||||
|
Built on top of this foundation, Relm4 makes developing more idiomatic, simpler and faster and enables you to become productive in just a few hours.
|
||||||
|
|
||||||
|
## Our goals
|
||||||
|
|
||||||
|
+ ⏱️ **Productivity**
|
||||||
|
+ ✨ **Simplicity**
|
||||||
|
+ 📎 **Outstanding documentation**
|
||||||
|
+ 🔧 **Maintainability**
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
+ 📖 **[The Relm4 book](https://relm4.org/book/stable/)**
|
||||||
|
+ 📜 **[Rust documentation](https://docs.rs/relm4/)**
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
Relm4 depends on GTK4: [How to install GTK4 and Rust](https://gtk-rs.org/gtk4-rs/git/book/installation.html)
|
||||||
|
|
||||||
|
## Ecosystem
|
||||||
|
|
||||||
|
+ [relm4-macros](https://crates.io/crates/relm4-macros) - several macros for declarative UI definitions.
|
||||||
|
+ [relm4-components](https://crates.io/crates/relm4-components) - a collections of reusable components.
|
||||||
|
+ [relm4-icons](https://crates.io/crates/relm4-icons) - icons for your application.
|
||||||
|
+ [relm4-template](https://github.com/Relm4/relm4-template) - a starter template for creating Relm4 applications in the Flatpak package format.
|
||||||
|
+ [relm4-snippets](https://github.com/Relm4/vscode-relm4-snippets) - code snippets to speed up your development.
|
||||||
|
|
||||||
|
Use this in to your `Cargo.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# Core library
|
||||||
|
relm4 = "0.6.2"
|
||||||
|
# Optional: reusable components
|
||||||
|
relm4-components = "0.6.2"
|
||||||
|
# Optional: icons
|
||||||
|
relm4-icons = { version = "0.6.0", features = ["plus"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
The `relm4` crate has four feature flags:
|
||||||
|
|
||||||
|
| Flag | Purpose | Default |
|
||||||
|
| :--- | :------ | :-----: |
|
||||||
|
| `macros` | Enable macros by re-exporting [`relm4-macros`](https://crates.io/crates/relm4-macros) | ✅ |
|
||||||
|
| `libadwaita` | Improved support for [libadwaita](https://gitlab.gnome.org/World/Rust/libadwaita-rs) | - |
|
||||||
|
| `libpanel` | Improved support for [libpanel](https://gitlab.gnome.org/World/Rust/libpanel-rs) | - |
|
||||||
|
| `dox` | Linking to the underlying C libraries is skipped to allow building the docs without dependencies | - |
|
||||||
|
| `gnome_44` | Enable all version feature flags of all dependencies to match the GNOME 44 SDK | - |
|
||||||
|
| `gnome_43` | Enable all version feature flags of all dependencies to match the GNOME 43 SDK | - |
|
||||||
|
| `gnome_42` | Enable all version feature flags of all dependencies to match the GNOME 42 SDK | - |
|
||||||
|
|
||||||
|
The `macros` feature is a default feature.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
Several example applications are available at [examples/](examples/).
|
||||||
|
|
||||||
|
#### [📸 Screenshots from the example apps](assets/screenshots)
|
||||||
|
|
||||||
|
### A simple counter app
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
```rust
|
||||||
|
use gtk::prelude::*;
|
||||||
|
use relm4::prelude::*;
|
||||||
|
|
||||||
|
struct App {
|
||||||
|
counter: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Msg {
|
||||||
|
Increment,
|
||||||
|
Decrement,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[relm4::component]
|
||||||
|
impl SimpleComponent for App {
|
||||||
|
type Init = u8;
|
||||||
|
type Input = Msg;
|
||||||
|
type Output = ();
|
||||||
|
|
||||||
|
view! {
|
||||||
|
gtk::Window {
|
||||||
|
set_title: Some("Simple app"),
|
||||||
|
set_default_size: (300, 100),
|
||||||
|
|
||||||
|
gtk::Box {
|
||||||
|
set_orientation: gtk::Orientation::Vertical,
|
||||||
|
set_spacing: 5,
|
||||||
|
set_margin_all: 5,
|
||||||
|
|
||||||
|
gtk::Button {
|
||||||
|
set_label: "Increment",
|
||||||
|
connect_clicked => Msg::Increment,
|
||||||
|
},
|
||||||
|
|
||||||
|
gtk::Button {
|
||||||
|
set_label: "Decrement",
|
||||||
|
connect_clicked => Msg::Decrement,
|
||||||
|
},
|
||||||
|
|
||||||
|
gtk::Label {
|
||||||
|
#[watch]
|
||||||
|
set_label: &format!("Counter: {}", model.counter),
|
||||||
|
set_margin_all: 5,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the component.
|
||||||
|
fn init(
|
||||||
|
counter: Self::Init,
|
||||||
|
root: &Self::Root,
|
||||||
|
sender: ComponentSender<Self>,
|
||||||
|
) -> ComponentParts<Self> {
|
||||||
|
let model = App { counter };
|
||||||
|
|
||||||
|
// Insert the code generation of the view! macro here
|
||||||
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
ComponentParts { model, widgets }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
|
||||||
|
match msg {
|
||||||
|
Msg::Increment => {
|
||||||
|
self.counter = self.counter.wrapping_add(1);
|
||||||
|
}
|
||||||
|
Msg::Decrement => {
|
||||||
|
self.counter = self.counter.wrapping_sub(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let app = RelmApp::new("relm4.example.simple");
|
||||||
|
app.run::<App>(0);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Projects using Relm4
|
||||||
|
|
||||||
|
- [fm](https://github.com/euclio/fm) — A small, general-purpose file manager.
|
||||||
|
- [Done](https://github.com/edfloreshz/done) - A simple and versatile to do app.
|
||||||
|
- [Reovim](https://github.com/songww/reovim) - GUI frontend for neovim.
|
||||||
|
- [NixOS Configuration Editor](https://github.com/vlinkz/nixos-conf-editor) - A graphical configuration editor for [NixOS](https://nixos.org).
|
||||||
|
- [Rhino Setup](https://github.com/rhino-linux/rhino-setup) - Setup wizard for [Rolling Rhino](https://rhinolinux.org/)
|
||||||
|
- [Lemoa](https://github.com/lemmy-gtk/lemoa) - Desktop client for Lemmy
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Licensed under either of
|
||||||
|
|
||||||
|
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
at your option.
|
||||||
|
|
||||||
|
### Contribution
|
||||||
|
|
||||||
|
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||||
|
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any
|
||||||
|
additional terms or conditions.
|
||||||
|
|
||||||
|
**Feedback and contributions are highly appreciated!**
|
||||||
17
Relm4-0.6.2/publish.sh
Normal file
17
Relm4-0.6.2/publish.sh
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Exit on error
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Check the code
|
||||||
|
cargo update
|
||||||
|
cargo fmt --all -- --check
|
||||||
|
cargo clippy --all-targets -- --deny warnings
|
||||||
|
cargo clippy --features "all" -- --deny warnings
|
||||||
|
cargo clippy --examples -- --deny warnings
|
||||||
|
cargo test
|
||||||
|
|
||||||
|
# Publish and pass all arguments to cargo
|
||||||
|
cargo publish -p relm4-macros
|
||||||
|
cargo publish -p relm4
|
||||||
|
cargo publish -p relm4-components
|
||||||
38
Relm4-0.6.2/relm4-components/Cargo.toml
Normal file
38
Relm4-0.6.2/relm4-components/Cargo.toml
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
[package]
|
||||||
|
name = "relm4-components"
|
||||||
|
readme = "README.md"
|
||||||
|
documentation = "https://docs.rs/relm4_components/"
|
||||||
|
|
||||||
|
version.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
description.workspace = true
|
||||||
|
|
||||||
|
homepage.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
|
||||||
|
keywords.workspace = true
|
||||||
|
categories.workspace = true
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
# enable unstable features in the documentation
|
||||||
|
rustc-args = ["--cfg", "docsrs"]
|
||||||
|
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
once_cell = "1.18"
|
||||||
|
relm4 = { version = "0.6.2", path = "../relm4", default-features = false, features = ["macros"] }
|
||||||
|
reqwest = { version = "0.11.18", optional = true }
|
||||||
|
tracker = "0.2.1"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
dox = ["relm4/dox", "web"]
|
||||||
|
web = ["reqwest"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "web_image"
|
||||||
|
required-features = ["web"]
|
||||||
6
Relm4-0.6.2/relm4-components/README.md
Normal file
6
Relm4-0.6.2/relm4-components/README.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# Relm4 components
|
||||||
|
|
||||||
|
[](https://crates.io/crates/relm4-components)
|
||||||
|
[](https://docs.rs/relm4_components/)
|
||||||
|
|
||||||
|
A collection of reusable components that can easily be integrated into Relm4 applications.
|
||||||
1
Relm4-0.6.2/relm4-components/examples/.gitignore
vendored
Normal file
1
Relm4-0.6.2/relm4-components/examples/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.recent_files
|
||||||
148
Relm4-0.6.2/relm4-components/examples/alert.rs
Normal file
148
Relm4-0.6.2/relm4-components/examples/alert.rs
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
use gtk::prelude::*;
|
||||||
|
use relm4::{
|
||||||
|
gtk, Component, ComponentController, ComponentParts, ComponentSender, Controller, RelmApp,
|
||||||
|
RelmWidgetExt, SimpleComponent,
|
||||||
|
};
|
||||||
|
use relm4_components::alert::{Alert, AlertMsg, AlertResponse, AlertSettings};
|
||||||
|
|
||||||
|
struct App {
|
||||||
|
counter: u8,
|
||||||
|
alert_toggle: bool,
|
||||||
|
dialog: Controller<Alert>,
|
||||||
|
second_dialog: Controller<Alert>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum AppMsg {
|
||||||
|
Increment,
|
||||||
|
Decrement,
|
||||||
|
CloseRequest,
|
||||||
|
Save,
|
||||||
|
Close,
|
||||||
|
Ignore,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[relm4::component]
|
||||||
|
impl SimpleComponent for App {
|
||||||
|
type Init = ();
|
||||||
|
type Input = AppMsg;
|
||||||
|
type Output = ();
|
||||||
|
|
||||||
|
view! {
|
||||||
|
main_window = gtk::ApplicationWindow {
|
||||||
|
set_title: Some("Alert example"),
|
||||||
|
set_default_size: (300, 100),
|
||||||
|
|
||||||
|
connect_close_request[sender] => move |_| {
|
||||||
|
sender.input(AppMsg::CloseRequest);
|
||||||
|
gtk::Inhibit(true)
|
||||||
|
},
|
||||||
|
|
||||||
|
gtk::Box {
|
||||||
|
set_orientation: gtk::Orientation::Vertical,
|
||||||
|
set_margin_all: 5,
|
||||||
|
set_spacing: 5,
|
||||||
|
|
||||||
|
append = >k::Button {
|
||||||
|
set_label: "Increment",
|
||||||
|
connect_clicked => AppMsg::Increment,
|
||||||
|
},
|
||||||
|
append = >k::Button {
|
||||||
|
set_label: "Decrement",
|
||||||
|
connect_clicked => AppMsg::Decrement,
|
||||||
|
},
|
||||||
|
append = >k::Label {
|
||||||
|
set_margin_all: 5,
|
||||||
|
#[watch]
|
||||||
|
set_label: &format!("Counter: {}", model.counter),
|
||||||
|
},
|
||||||
|
append = >k::Button {
|
||||||
|
set_label: "Close",
|
||||||
|
connect_clicked => AppMsg::CloseRequest,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, msg: AppMsg, _sender: ComponentSender<Self>) {
|
||||||
|
match msg {
|
||||||
|
AppMsg::Increment => {
|
||||||
|
self.counter = self.counter.wrapping_add(1);
|
||||||
|
}
|
||||||
|
AppMsg::Decrement => {
|
||||||
|
self.counter = self.counter.wrapping_sub(1);
|
||||||
|
}
|
||||||
|
AppMsg::CloseRequest => {
|
||||||
|
if self.counter == 42 {
|
||||||
|
relm4::main_application().quit();
|
||||||
|
} else {
|
||||||
|
self.alert_toggle = !self.alert_toggle;
|
||||||
|
if self.alert_toggle {
|
||||||
|
self.dialog.emit(AlertMsg::Show);
|
||||||
|
} else {
|
||||||
|
self.second_dialog.emit(AlertMsg::Show);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AppMsg::Save => {
|
||||||
|
println!("* Open save dialog here *");
|
||||||
|
}
|
||||||
|
AppMsg::Close => {
|
||||||
|
relm4::main_application().quit();
|
||||||
|
}
|
||||||
|
AppMsg::Ignore => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
_: Self::Init,
|
||||||
|
root: &Self::Root,
|
||||||
|
sender: ComponentSender<Self>,
|
||||||
|
) -> ComponentParts<Self> {
|
||||||
|
let model = App {
|
||||||
|
counter: 0,
|
||||||
|
alert_toggle: false,
|
||||||
|
dialog: Alert::builder()
|
||||||
|
.transient_for(root)
|
||||||
|
.launch(AlertSettings {
|
||||||
|
text: String::from("Do you want to quit without saving? (First alert)"),
|
||||||
|
secondary_text: Some(String::from("Your counter hasn't reached 42 yet")),
|
||||||
|
confirm_label: String::from("Close without saving"),
|
||||||
|
cancel_label: String::from("Cancel"),
|
||||||
|
option_label: Some(String::from("Save")),
|
||||||
|
is_modal: true,
|
||||||
|
destructive_accept: true,
|
||||||
|
})
|
||||||
|
.forward(sender.input_sender(), convert_alert_response),
|
||||||
|
second_dialog: Alert::builder()
|
||||||
|
.transient_for(root)
|
||||||
|
.launch(AlertSettings {
|
||||||
|
text: String::from("Do you want to quit without saving? (Second alert)"),
|
||||||
|
secondary_text: Some(String::from("Your counter hasn't reached 42 yet")),
|
||||||
|
confirm_label: String::from("Close without saving"),
|
||||||
|
cancel_label: String::from("Cancel"),
|
||||||
|
option_label: Some(String::from("Save")),
|
||||||
|
is_modal: true,
|
||||||
|
destructive_accept: true,
|
||||||
|
})
|
||||||
|
.forward(sender.input_sender(), convert_alert_response),
|
||||||
|
};
|
||||||
|
|
||||||
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
ComponentParts { model, widgets }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_alert_response(response: AlertResponse) -> AppMsg {
|
||||||
|
match response {
|
||||||
|
AlertResponse::Confirm => AppMsg::Close,
|
||||||
|
AlertResponse::Cancel => AppMsg::Ignore,
|
||||||
|
AlertResponse::Option => AppMsg::Save,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let app = RelmApp::new("relm4.example.alert");
|
||||||
|
app.run::<App>(());
|
||||||
|
}
|
||||||
176
Relm4-0.6.2/relm4-components/examples/file_dialogs.rs
Normal file
176
Relm4-0.6.2/relm4-components/examples/file_dialogs.rs
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
use gtk::prelude::*;
|
||||||
|
use relm4::{
|
||||||
|
gtk, Component, ComponentController, ComponentParts, ComponentSender, Controller, RelmApp,
|
||||||
|
RelmWidgetExt, SimpleComponent,
|
||||||
|
};
|
||||||
|
use relm4_components::{open_dialog::*, save_dialog::*};
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
struct App {
|
||||||
|
open_dialog: Controller<OpenDialog>,
|
||||||
|
save_dialog: Controller<SaveDialog>,
|
||||||
|
buffer: gtk::TextBuffer,
|
||||||
|
file_name: Option<String>,
|
||||||
|
message: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Input {
|
||||||
|
OpenRequest,
|
||||||
|
OpenResponse(PathBuf),
|
||||||
|
SaveRequest,
|
||||||
|
SaveResponse(PathBuf),
|
||||||
|
ShowMessage(String),
|
||||||
|
ResetMessage,
|
||||||
|
Ignore,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[relm4::component]
|
||||||
|
impl SimpleComponent for App {
|
||||||
|
type Init = ();
|
||||||
|
type Input = Input;
|
||||||
|
type Output = ();
|
||||||
|
|
||||||
|
view! {
|
||||||
|
root = gtk::ApplicationWindow {
|
||||||
|
set_default_size: (600, 400),
|
||||||
|
|
||||||
|
#[watch]
|
||||||
|
set_title: Some(model.file_name.as_deref().unwrap_or_default()),
|
||||||
|
|
||||||
|
#[wrap(Some)]
|
||||||
|
set_titlebar = >k::HeaderBar {
|
||||||
|
pack_start = >k::Button {
|
||||||
|
set_label: "Open",
|
||||||
|
connect_clicked => Input::OpenRequest,
|
||||||
|
},
|
||||||
|
pack_end = >k::Button {
|
||||||
|
set_label: "Save As",
|
||||||
|
connect_clicked => Input::SaveRequest,
|
||||||
|
|
||||||
|
#[watch]
|
||||||
|
set_sensitive: model.file_name.is_some(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
gtk::Box {
|
||||||
|
set_orientation: gtk::Orientation::Vertical,
|
||||||
|
set_margin_all: 5,
|
||||||
|
|
||||||
|
gtk::ScrolledWindow {
|
||||||
|
set_min_content_height: 380,
|
||||||
|
|
||||||
|
#[wrap(Some)]
|
||||||
|
set_child = >k::TextView {
|
||||||
|
set_buffer: Some(&model.buffer),
|
||||||
|
|
||||||
|
#[watch]
|
||||||
|
set_visible: model.file_name.is_some(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn post_view() {
|
||||||
|
if let Some(text) = &model.message {
|
||||||
|
let dialog = gtk::MessageDialog::builder()
|
||||||
|
.text(text)
|
||||||
|
.use_markup(true)
|
||||||
|
.transient_for(&widgets.root)
|
||||||
|
.modal(true)
|
||||||
|
.buttons(gtk::ButtonsType::Ok)
|
||||||
|
.build();
|
||||||
|
dialog.connect_response(|dialog, _| dialog.destroy());
|
||||||
|
dialog.set_visible(true);
|
||||||
|
sender.input(Input::ResetMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
_: Self::Init,
|
||||||
|
root: &Self::Root,
|
||||||
|
sender: ComponentSender<Self>,
|
||||||
|
) -> ComponentParts<Self> {
|
||||||
|
let open_dialog = OpenDialog::builder()
|
||||||
|
.transient_for_native(root)
|
||||||
|
.launch(OpenDialogSettings::default())
|
||||||
|
.forward(sender.input_sender(), |response| match response {
|
||||||
|
OpenDialogResponse::Accept(path) => Input::OpenResponse(path),
|
||||||
|
OpenDialogResponse::Cancel => Input::Ignore,
|
||||||
|
});
|
||||||
|
|
||||||
|
let save_dialog = SaveDialog::builder()
|
||||||
|
.transient_for_native(root)
|
||||||
|
.launch(SaveDialogSettings::default())
|
||||||
|
.forward(sender.input_sender(), |response| match response {
|
||||||
|
SaveDialogResponse::Accept(path) => Input::SaveResponse(path),
|
||||||
|
SaveDialogResponse::Cancel => Input::Ignore,
|
||||||
|
});
|
||||||
|
|
||||||
|
let model = App {
|
||||||
|
open_dialog,
|
||||||
|
save_dialog,
|
||||||
|
buffer: gtk::TextBuffer::new(None),
|
||||||
|
file_name: None,
|
||||||
|
message: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
sender.input(Input::ShowMessage(String::from(
|
||||||
|
"A simple text editor showing the usage of\n<b>OpenFileDialog</b> and <b>SaveFileDialog</b> components.\n\nStart by clicking <b>Open</b> on the header bar.",
|
||||||
|
)));
|
||||||
|
|
||||||
|
ComponentParts { model, widgets }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {
|
||||||
|
match message {
|
||||||
|
Input::OpenRequest => self.open_dialog.emit(OpenDialogMsg::Open),
|
||||||
|
Input::OpenResponse(path) => match std::fs::read_to_string(&path) {
|
||||||
|
Ok(contents) => {
|
||||||
|
self.buffer.set_text(&contents);
|
||||||
|
self.file_name = Some(
|
||||||
|
path.file_name()
|
||||||
|
.expect("The path has no file name")
|
||||||
|
.to_str()
|
||||||
|
.expect("Cannot convert file name to string")
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(e) => sender.input(Input::ShowMessage(e.to_string())),
|
||||||
|
},
|
||||||
|
Input::SaveRequest => self
|
||||||
|
.save_dialog
|
||||||
|
.emit(SaveDialogMsg::SaveAs(self.file_name.clone().unwrap())),
|
||||||
|
Input::SaveResponse(path) => match std::fs::write(
|
||||||
|
&path,
|
||||||
|
self.buffer
|
||||||
|
.text(&self.buffer.start_iter(), &self.buffer.end_iter(), false),
|
||||||
|
) {
|
||||||
|
Ok(_) => {
|
||||||
|
sender.input(Input::ShowMessage(format!(
|
||||||
|
"File saved successfully at {path:?}"
|
||||||
|
)));
|
||||||
|
self.buffer.set_text("");
|
||||||
|
self.file_name = None;
|
||||||
|
}
|
||||||
|
Err(e) => sender.input(Input::ShowMessage(e.to_string())),
|
||||||
|
},
|
||||||
|
Input::ShowMessage(message) => {
|
||||||
|
self.message = Some(message);
|
||||||
|
}
|
||||||
|
Input::ResetMessage => {
|
||||||
|
self.message = None;
|
||||||
|
}
|
||||||
|
Input::Ignore => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let app = RelmApp::new("relm4.example.file_dialogs");
|
||||||
|
app.run::<App>(());
|
||||||
|
}
|
||||||
69
Relm4-0.6.2/relm4-components/examples/open_button.rs
Normal file
69
Relm4-0.6.2/relm4-components/examples/open_button.rs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use gtk::prelude::*;
|
||||||
|
use relm4::{
|
||||||
|
gtk, Component, ComponentController, ComponentParts, ComponentSender, Controller, RelmApp,
|
||||||
|
SimpleComponent,
|
||||||
|
};
|
||||||
|
use relm4_components::open_button::{OpenButton, OpenButtonSettings};
|
||||||
|
use relm4_components::open_dialog::OpenDialogSettings;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum AppMsg {
|
||||||
|
Open(PathBuf),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct App {
|
||||||
|
open_button: Controller<OpenButton>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[relm4::component]
|
||||||
|
impl SimpleComponent for App {
|
||||||
|
type Init = ();
|
||||||
|
type Input = AppMsg;
|
||||||
|
type Output = ();
|
||||||
|
|
||||||
|
view! {
|
||||||
|
gtk::ApplicationWindow {
|
||||||
|
set_default_size: (300, 100),
|
||||||
|
|
||||||
|
#[wrap(Some)]
|
||||||
|
set_titlebar = >k::HeaderBar {
|
||||||
|
pack_start: model.open_button.widget(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, msg: Self::Input, _: ComponentSender<Self>) {
|
||||||
|
match msg {
|
||||||
|
AppMsg::Open(path) => {
|
||||||
|
println!("* Opened file {path:?} *");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
_: Self::Init,
|
||||||
|
root: &Self::Root,
|
||||||
|
sender: ComponentSender<Self>,
|
||||||
|
) -> ComponentParts<Self> {
|
||||||
|
let open_button = OpenButton::builder()
|
||||||
|
.launch(OpenButtonSettings {
|
||||||
|
dialog_settings: OpenDialogSettings::default(),
|
||||||
|
text: "Open file",
|
||||||
|
recently_opened_files: Some(".recent_files"),
|
||||||
|
max_recent_files: 10,
|
||||||
|
})
|
||||||
|
.forward(sender.input_sender(), AppMsg::Open);
|
||||||
|
let model = App { open_button };
|
||||||
|
|
||||||
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
ComponentParts { model, widgets }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let app = RelmApp::new("relm4.example.open_button");
|
||||||
|
app.run::<App>(());
|
||||||
|
}
|
||||||
84
Relm4-0.6.2/relm4-components/examples/web_image.rs
Normal file
84
Relm4-0.6.2/relm4-components/examples/web_image.rs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
use gtk::prelude::*;
|
||||||
|
use relm4::{
|
||||||
|
gtk, Component, ComponentController, ComponentParts, ComponentSender, Controller, RelmApp,
|
||||||
|
SimpleComponent,
|
||||||
|
};
|
||||||
|
use relm4_components::web_image::{WebImage, WebImageMsg};
|
||||||
|
|
||||||
|
const IMAGES: &[&str] = &[
|
||||||
|
"https://raw.githubusercontent.com/Relm4/Relm4/main/assets/Relm_logo_with_text.png",
|
||||||
|
"https://raw.githubusercontent.com/Relm4/Relm4/main/assets/Relm_logo.png",
|
||||||
|
"https://raw.githubusercontent.com/gtk-rs/gtk-rs.github.io/master/logo/gtk-rs.ico",
|
||||||
|
"https://avatars.githubusercontent.com/u/5430905",
|
||||||
|
];
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum AppMsg {
|
||||||
|
Next,
|
||||||
|
Unload,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct App {
|
||||||
|
image: Controller<WebImage>,
|
||||||
|
idx: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[relm4::component]
|
||||||
|
impl SimpleComponent for App {
|
||||||
|
type Init = ();
|
||||||
|
type Input = AppMsg;
|
||||||
|
type Output = ();
|
||||||
|
|
||||||
|
view! {
|
||||||
|
gtk::ApplicationWindow {
|
||||||
|
set_default_size: (300, 300),
|
||||||
|
|
||||||
|
#[wrap(Some)]
|
||||||
|
set_titlebar = >k::HeaderBar {
|
||||||
|
pack_start = >k::Button {
|
||||||
|
set_label: "Next image",
|
||||||
|
connect_clicked => AppMsg::Next,
|
||||||
|
},
|
||||||
|
pack_start = >k::Button {
|
||||||
|
set_label: "Unload image",
|
||||||
|
connect_clicked => AppMsg::Unload,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
gtk::Box {
|
||||||
|
#[local_ref]
|
||||||
|
image -> gtk::Box {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, msg: Self::Input, _: ComponentSender<Self>) {
|
||||||
|
match msg {
|
||||||
|
AppMsg::Next => {
|
||||||
|
self.idx = (self.idx + 1) % IMAGES.len();
|
||||||
|
self.image
|
||||||
|
.emit(WebImageMsg::LoadImage(IMAGES[self.idx].to_owned()));
|
||||||
|
}
|
||||||
|
AppMsg::Unload => self.image.emit(WebImageMsg::Unload),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
_: Self::Init,
|
||||||
|
root: &Self::Root,
|
||||||
|
sender: ComponentSender<Self>,
|
||||||
|
) -> ComponentParts<Self> {
|
||||||
|
let image = WebImage::builder().launch(IMAGES[0].to_owned()).detach();
|
||||||
|
let model = App { image, idx: 0 };
|
||||||
|
|
||||||
|
let image = model.image.widget();
|
||||||
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
ComponentParts { model, widgets }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let app = RelmApp::new("relm4.example.open_button");
|
||||||
|
app.run::<App>(());
|
||||||
|
}
|
||||||
129
Relm4-0.6.2/relm4-components/src/alert.rs
Normal file
129
Relm4-0.6.2/relm4-components/src/alert.rs
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
//! Reusable and easily configurable alert component.
|
||||||
|
//!
|
||||||
|
//! **[Example implementation](https://github.com/AaronErhardt/relm4/blob/main/relm4-examples/examples/alert.rs)**
|
||||||
|
|
||||||
|
use gtk::prelude::{DialogExt, GtkWindowExt, WidgetExt};
|
||||||
|
use relm4::{gtk, ComponentParts, ComponentSender, SimpleComponent};
|
||||||
|
|
||||||
|
/// Configuration for the alert dialog component
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AlertSettings {
|
||||||
|
/// Large text
|
||||||
|
pub text: String,
|
||||||
|
/// Optional secondary, smaller text
|
||||||
|
pub secondary_text: Option<String>,
|
||||||
|
/// Modal dialogs freeze other windows as long they are visible
|
||||||
|
pub is_modal: bool,
|
||||||
|
/// Sets color of the accept button to red if the theme supports it
|
||||||
|
pub destructive_accept: bool,
|
||||||
|
/// Text for confirm button
|
||||||
|
pub confirm_label: String,
|
||||||
|
/// Text for cancel button
|
||||||
|
pub cancel_label: String,
|
||||||
|
/// Text for third option button. If [`None`] the third button won't be created.
|
||||||
|
pub option_label: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Alert dialog component.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Alert {
|
||||||
|
settings: AlertSettings,
|
||||||
|
is_active: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Messages that can be sent to the alert dialog component
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum AlertMsg {
|
||||||
|
/// Message sent by the parent to view the dialog
|
||||||
|
Show,
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
Response(gtk::ResponseType),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// User action performed on the alert dialog.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum AlertResponse {
|
||||||
|
/// User clicked confirm button.
|
||||||
|
Confirm,
|
||||||
|
|
||||||
|
/// User clicked cancel button.
|
||||||
|
Cancel,
|
||||||
|
|
||||||
|
/// User clicked user-supplied option.
|
||||||
|
Option,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Widgets of the alert dialog component.
|
||||||
|
#[relm4::component(pub)]
|
||||||
|
impl SimpleComponent for Alert {
|
||||||
|
type Init = AlertSettings;
|
||||||
|
type Input = AlertMsg;
|
||||||
|
type Output = AlertResponse;
|
||||||
|
|
||||||
|
view! {
|
||||||
|
#[name(dialog)]
|
||||||
|
gtk::MessageDialog {
|
||||||
|
set_message_type: gtk::MessageType::Question,
|
||||||
|
#[watch]
|
||||||
|
set_visible: model.is_active,
|
||||||
|
connect_response[sender] => move |_, response| {
|
||||||
|
sender.input(AlertMsg::Response(response));
|
||||||
|
},
|
||||||
|
|
||||||
|
// Apply configuration
|
||||||
|
set_text: Some(&model.settings.text),
|
||||||
|
set_secondary_text: model.settings.secondary_text.as_deref(),
|
||||||
|
set_modal: model.settings.is_modal,
|
||||||
|
add_button: (&model.settings.confirm_label, gtk::ResponseType::Accept),
|
||||||
|
add_button: (&model.settings.cancel_label, gtk::ResponseType::Cancel),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
settings: AlertSettings,
|
||||||
|
root: &Self::Root,
|
||||||
|
sender: ComponentSender<Self>,
|
||||||
|
) -> ComponentParts<Self> {
|
||||||
|
let model = Alert {
|
||||||
|
settings,
|
||||||
|
is_active: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
if let Some(option_label) = &model.settings.option_label {
|
||||||
|
widgets
|
||||||
|
.dialog
|
||||||
|
.add_button(option_label, gtk::ResponseType::Other(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
if model.settings.destructive_accept {
|
||||||
|
let accept_widget = widgets
|
||||||
|
.dialog
|
||||||
|
.widget_for_response(gtk::ResponseType::Accept)
|
||||||
|
.expect("No button for accept response set");
|
||||||
|
accept_widget.add_css_class("destructive-action");
|
||||||
|
}
|
||||||
|
|
||||||
|
ComponentParts { model, widgets }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, input: AlertMsg, sender: ComponentSender<Self>) {
|
||||||
|
match input {
|
||||||
|
AlertMsg::Show => {
|
||||||
|
self.is_active = true;
|
||||||
|
}
|
||||||
|
AlertMsg::Response(ty) => {
|
||||||
|
self.is_active = false;
|
||||||
|
sender
|
||||||
|
.output(match ty {
|
||||||
|
gtk::ResponseType::Accept => AlertResponse::Confirm,
|
||||||
|
gtk::ResponseType::Other(_) => AlertResponse::Option,
|
||||||
|
_ => AlertResponse::Cancel,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
Relm4-0.6.2/relm4-components/src/lib.rs
Normal file
43
Relm4-0.6.2/relm4-components/src/lib.rs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
//! Collection of reusable and easily configurable components for Relm4.
|
||||||
|
//!
|
||||||
|
//! Docs of related crates:
|
||||||
|
//! [relm4](https://docs.rs/relm4)
|
||||||
|
//! | [relm4-macros](https://docs.rs/relm4_macros)
|
||||||
|
//! | [relm4-components](https://docs.rs/relm4_components)
|
||||||
|
//! | [gtk4-rs](https://gtk-rs.org/gtk4-rs/git/docs)
|
||||||
|
//! | [gtk-rs-core](https://gtk-rs.org/gtk-rs-core/git/docs)
|
||||||
|
//! | [libadwaita-rs](https://world.pages.gitlab.gnome.org/Rust/libadwaita-rs/git/docs/libadwaita)
|
||||||
|
//! | [libpanel-rs](https://world.pages.gitlab.gnome.org/Rust/libpanel-rs/git/docs/libpanel)
|
||||||
|
//!
|
||||||
|
//! [GitHub](https://github.com/Relm4/Relm4)
|
||||||
|
//! | [Website](https://relm4.org)
|
||||||
|
//! | [Book](https://relm4.org/book/stable/)
|
||||||
|
//! | [Blog](https://relm4.org/blog)
|
||||||
|
|
||||||
|
#![doc(html_logo_url = "https://relm4.org/icons/relm4_logo.svg")]
|
||||||
|
#![doc(html_favicon_url = "https://relm4.org/icons/relm4_org.svg")]
|
||||||
|
#![warn(
|
||||||
|
missing_debug_implementations,
|
||||||
|
missing_docs,
|
||||||
|
rust_2018_idioms,
|
||||||
|
unreachable_pub,
|
||||||
|
unused_qualifications,
|
||||||
|
clippy::cargo,
|
||||||
|
clippy::must_use_candidate
|
||||||
|
)]
|
||||||
|
// Configuration for doc builds on the nightly toolchain.
|
||||||
|
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||||
|
// Ignore GTK 4.10 deprecations.
|
||||||
|
// Most deprecated features can only be replaced with new 4.10 APIs and
|
||||||
|
// we don't want to lift the minimum requirement GTK4 version for Relm4 yet.
|
||||||
|
#![allow(deprecated)]
|
||||||
|
|
||||||
|
pub mod alert;
|
||||||
|
pub mod open_button;
|
||||||
|
pub mod open_dialog;
|
||||||
|
pub mod save_dialog;
|
||||||
|
pub mod simple_combo_box;
|
||||||
|
|
||||||
|
#[cfg(feature = "web")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "web")))]
|
||||||
|
pub mod web_image;
|
||||||
42
Relm4-0.6.2/relm4-components/src/open_button/factory.rs
Normal file
42
Relm4-0.6.2/relm4-components/src/open_button/factory.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
use gtk::prelude::*;
|
||||||
|
use relm4::factory::{DynamicIndex, FactoryComponent, FactorySender};
|
||||||
|
use relm4::{gtk, RelmWidgetExt};
|
||||||
|
|
||||||
|
use super::OpenButtonMsg;
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct FileListItem {
|
||||||
|
pub(crate) path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[relm4::factory(pub(crate))]
|
||||||
|
impl FactoryComponent for FileListItem {
|
||||||
|
type ParentInput = OpenButtonMsg;
|
||||||
|
type CommandOutput = ();
|
||||||
|
type Input = ();
|
||||||
|
type Init = PathBuf;
|
||||||
|
type ParentWidget = gtk::Box;
|
||||||
|
type Output = OpenButtonMsg;
|
||||||
|
|
||||||
|
view! {
|
||||||
|
gtk::ListBoxRow {
|
||||||
|
gtk::Button {
|
||||||
|
set_label: self.path.iter().last().expect("Empty path").to_str().unwrap(),
|
||||||
|
set_margin_all: 0,
|
||||||
|
connect_clicked[sender, index] => move |_| {
|
||||||
|
sender.output(OpenButtonMsg::OpenRecent(index.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn forward_to_parent(output: Self::Output) -> Option<Self::ParentInput> {
|
||||||
|
Some(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_model(init: Self::Init, _: &DynamicIndex, _: FactorySender<Self>) -> Self {
|
||||||
|
Self { path: init }
|
||||||
|
}
|
||||||
|
}
|
||||||
192
Relm4-0.6.2/relm4-components/src/open_button/mod.rs
Normal file
192
Relm4-0.6.2/relm4-components/src/open_button/mod.rs
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
//! Reusable and easily configurable open button dialog component.
|
||||||
|
//!
|
||||||
|
//! **[Example implementation](https://github.com/Relm4/Relm4/blob/main/relm4-components/examples/open_button.rs)**
|
||||||
|
use relm4::factory::{DynamicIndex, FactoryVecDeque};
|
||||||
|
use relm4::gtk::prelude::*;
|
||||||
|
use relm4::{
|
||||||
|
gtk, Component, ComponentController, ComponentParts, ComponentSender, Controller,
|
||||||
|
SimpleComponent,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::open_dialog::{OpenDialog, OpenDialogMsg, OpenDialogResponse, OpenDialogSettings};
|
||||||
|
|
||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
mod factory;
|
||||||
|
|
||||||
|
use factory::FileListItem;
|
||||||
|
|
||||||
|
/// Open button component.
|
||||||
|
///
|
||||||
|
/// Creates a button with custom text that can be used to open a file chooser dialog. If a file is
|
||||||
|
/// chosen, then it will be emitted as an output. The component can also optionally display a
|
||||||
|
/// popover list of open files if [`OpenButtonSettings::recently_opened_files`] is set to a value.
|
||||||
|
#[tracker::track]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct OpenButton {
|
||||||
|
#[do_not_track]
|
||||||
|
config: OpenButtonSettings,
|
||||||
|
#[do_not_track]
|
||||||
|
dialog: Controller<OpenDialog>,
|
||||||
|
#[do_not_track]
|
||||||
|
recent_files: Option<FactoryVecDeque<FileListItem>>,
|
||||||
|
initialized: bool,
|
||||||
|
#[do_not_track]
|
||||||
|
reset_popover: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// Configuration for the open button component
|
||||||
|
pub struct OpenButtonSettings {
|
||||||
|
/// Settings for the open file dialog.
|
||||||
|
pub dialog_settings: OpenDialogSettings,
|
||||||
|
/// Text of the open button.
|
||||||
|
pub text: &'static str,
|
||||||
|
/// Path to a file where recent files should be stored.
|
||||||
|
/// This list is updated fully automatically.
|
||||||
|
pub recently_opened_files: Option<&'static str>,
|
||||||
|
/// Maximum amount of recent files to store.
|
||||||
|
/// This is only used if a path for storing the recently opened files was set.
|
||||||
|
pub max_recent_files: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum OpenButtonMsg {
|
||||||
|
Open(PathBuf),
|
||||||
|
OpenRecent(DynamicIndex),
|
||||||
|
ShowDialog,
|
||||||
|
Ignore,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Widgets of the open button component
|
||||||
|
#[relm4::component(pub)]
|
||||||
|
impl SimpleComponent for OpenButton {
|
||||||
|
type Init = OpenButtonSettings;
|
||||||
|
type Input = OpenButtonMsg;
|
||||||
|
type Output = PathBuf;
|
||||||
|
|
||||||
|
view! {
|
||||||
|
gtk::Box {
|
||||||
|
add_css_class: "linked",
|
||||||
|
gtk::Button {
|
||||||
|
set_label: model.config.text,
|
||||||
|
connect_clicked => OpenButtonMsg::ShowDialog,
|
||||||
|
},
|
||||||
|
gtk::MenuButton {
|
||||||
|
set_visible: model.config.recently_opened_files.is_some(),
|
||||||
|
|
||||||
|
#[wrap(Some)]
|
||||||
|
#[name(popover)]
|
||||||
|
set_popover = >k::Popover {
|
||||||
|
gtk::ScrolledWindow {
|
||||||
|
set_hscrollbar_policy: gtk::PolicyType::Never,
|
||||||
|
set_min_content_width: 100,
|
||||||
|
set_min_content_height: 100,
|
||||||
|
set_min_content_height: 300,
|
||||||
|
|
||||||
|
#[name(recent_files_list)]
|
||||||
|
gtk::Box {
|
||||||
|
set_orientation: gtk::Orientation::Vertical,
|
||||||
|
set_vexpand: true,
|
||||||
|
set_hexpand: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>) {
|
||||||
|
self.reset_popover = false;
|
||||||
|
|
||||||
|
match msg {
|
||||||
|
OpenButtonMsg::ShowDialog => {
|
||||||
|
self.dialog.emit(OpenDialogMsg::Open);
|
||||||
|
}
|
||||||
|
OpenButtonMsg::Open(path) => {
|
||||||
|
sender.output(path.clone()).unwrap();
|
||||||
|
self.reset_popover = true;
|
||||||
|
|
||||||
|
if let Some(recent_files) = &mut self.recent_files {
|
||||||
|
let index = recent_files.iter().position(|item| item.path == path);
|
||||||
|
|
||||||
|
if let Some(index) = index {
|
||||||
|
recent_files.guard().remove(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
if recent_files.len() < self.config.max_recent_files {
|
||||||
|
recent_files.guard().push_front(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
let contents = recent_files
|
||||||
|
.iter()
|
||||||
|
.filter_map(|recent_path| {
|
||||||
|
recent_path.path.to_str().map(|s| format!("{s}\n"))
|
||||||
|
})
|
||||||
|
.collect::<String>();
|
||||||
|
|
||||||
|
let _ = fs::write(self.config.recently_opened_files.unwrap(), contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OpenButtonMsg::OpenRecent(index) => {
|
||||||
|
if let Some(item) = self
|
||||||
|
.recent_files
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|recent_files| recent_files.get(index.current_index()))
|
||||||
|
{
|
||||||
|
sender.input(OpenButtonMsg::Open(PathBuf::from(&item.path)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OpenButtonMsg::Ignore => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pre_view() {
|
||||||
|
if self.reset_popover {
|
||||||
|
popover.popdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
settings: Self::Init,
|
||||||
|
root: &Self::Root,
|
||||||
|
sender: ComponentSender<Self>,
|
||||||
|
) -> ComponentParts<Self> {
|
||||||
|
let dialog = OpenDialog::builder()
|
||||||
|
.transient_for_native(root)
|
||||||
|
.launch(settings.dialog_settings.clone())
|
||||||
|
.forward(sender.input_sender(), |response| match response {
|
||||||
|
OpenDialogResponse::Accept(path) => OpenButtonMsg::Open(path),
|
||||||
|
OpenDialogResponse::Cancel => OpenButtonMsg::Ignore,
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut model = Self {
|
||||||
|
config: settings,
|
||||||
|
dialog,
|
||||||
|
initialized: false,
|
||||||
|
recent_files: None,
|
||||||
|
reset_popover: false,
|
||||||
|
tracker: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
if let Some(filename) = model.config.recently_opened_files {
|
||||||
|
let mut factory =
|
||||||
|
FactoryVecDeque::new(widgets.recent_files_list.clone(), sender.input_sender());
|
||||||
|
|
||||||
|
if let Ok(entries) = fs::read_to_string(filename) {
|
||||||
|
let mut guard = factory.guard();
|
||||||
|
for entry in entries.lines() {
|
||||||
|
guard.push_back(PathBuf::from(entry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
model.recent_files = Some(factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
ComponentParts { model, widgets }
|
||||||
|
}
|
||||||
|
}
|
||||||
181
Relm4-0.6.2/relm4-components/src/open_dialog.rs
Normal file
181
Relm4-0.6.2/relm4-components/src/open_dialog.rs
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
//! Reusable and easily configurable open dialog component.
|
||||||
|
//!
|
||||||
|
//! **[Example implementation](https://github.com/Relm4/Relm4/blob/main/relm4-components/examples/file_dialogs.rs)**
|
||||||
|
use gtk::prelude::{Cast, FileChooserExt, FileExt, ListModelExt, NativeDialogExt};
|
||||||
|
use relm4::{gtk, ComponentParts, ComponentSender, SimpleComponent};
|
||||||
|
|
||||||
|
use std::{fmt::Debug, marker::PhantomData, path::PathBuf};
|
||||||
|
|
||||||
|
/// A component that prompts the user to choose a file.
|
||||||
|
///
|
||||||
|
/// The user would be able to select a single file. If you'd like to select multiple, use [`OpenDialogMulti`].
|
||||||
|
pub type OpenDialog = OpenDialogInner<SingleSelection>;
|
||||||
|
|
||||||
|
/// A component that prompts the user to choose a file.
|
||||||
|
///
|
||||||
|
/// The user would be able to select multiple files. If you'd like to select just one, use [`OpenDialog`].
|
||||||
|
pub type OpenDialogMulti = OpenDialogInner<MultiSelection>;
|
||||||
|
|
||||||
|
/// Type of selection used for the open dialog.
|
||||||
|
pub trait Select: Debug {
|
||||||
|
/// Output of the selection.
|
||||||
|
type Selection: Debug;
|
||||||
|
/// Whether to select multiple files inside the dialog.
|
||||||
|
const SELECT_MULTIPLE: bool;
|
||||||
|
/// Construct selection from the file chooser.
|
||||||
|
fn select(dialog: >k::FileChooserNative) -> Self::Selection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A type of selection where only one file can be chosen at a time.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SingleSelection;
|
||||||
|
|
||||||
|
impl Select for SingleSelection {
|
||||||
|
type Selection = PathBuf;
|
||||||
|
const SELECT_MULTIPLE: bool = false;
|
||||||
|
fn select(dialog: >k::FileChooserNative) -> Self::Selection {
|
||||||
|
dialog
|
||||||
|
.file()
|
||||||
|
.expect("No file selected")
|
||||||
|
.path()
|
||||||
|
.expect("No path")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A type of selection where multiple types can be chosen at a time.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct MultiSelection;
|
||||||
|
impl Select for MultiSelection {
|
||||||
|
type Selection = Vec<PathBuf>;
|
||||||
|
const SELECT_MULTIPLE: bool = true;
|
||||||
|
fn select(dialog: >k::FileChooserNative) -> Self::Selection {
|
||||||
|
let list_model = dialog.files();
|
||||||
|
(0..list_model.n_items())
|
||||||
|
.filter_map(|index| list_model.item(index))
|
||||||
|
.filter_map(|obj| obj.downcast::<gtk::gio::File>().ok())
|
||||||
|
.filter_map(|file| file.path())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
/// Configuration for the open dialog component
|
||||||
|
pub struct OpenDialogSettings {
|
||||||
|
/// Select folders instead of files
|
||||||
|
pub folder_mode: bool,
|
||||||
|
/// Label for cancel button
|
||||||
|
pub cancel_label: String,
|
||||||
|
/// Label for accept button
|
||||||
|
pub accept_label: String,
|
||||||
|
/// Allow or disallow creating folders
|
||||||
|
pub create_folders: bool,
|
||||||
|
/// Freeze other windows while the dialog is open
|
||||||
|
pub is_modal: bool,
|
||||||
|
/// Filter for MIME types or other patterns
|
||||||
|
pub filters: Vec<gtk::FileFilter>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for OpenDialogSettings {
|
||||||
|
fn default() -> Self {
|
||||||
|
OpenDialogSettings {
|
||||||
|
folder_mode: false,
|
||||||
|
accept_label: String::from("Open"),
|
||||||
|
cancel_label: String::from("Cancel"),
|
||||||
|
create_folders: true,
|
||||||
|
is_modal: true,
|
||||||
|
filters: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// Model for the open dialog component
|
||||||
|
pub struct OpenDialogInner<S: Select> {
|
||||||
|
visible: bool,
|
||||||
|
_phantom: PhantomData<S>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Messages that can be sent to the open dialog component
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum OpenDialogMsg {
|
||||||
|
/// Show the dialog
|
||||||
|
Open,
|
||||||
|
#[doc(hidden)]
|
||||||
|
Hide,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Messages that can be sent from the open dialog component
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum OpenDialogResponse<S: Select> {
|
||||||
|
/// User clicked accept button.
|
||||||
|
Accept(S::Selection),
|
||||||
|
/// User clicked cancel button.
|
||||||
|
Cancel,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Widgets of the open dialog component.
|
||||||
|
#[relm4::component(pub)]
|
||||||
|
impl<S: Select + 'static> SimpleComponent for OpenDialogInner<S> {
|
||||||
|
type Init = OpenDialogSettings;
|
||||||
|
type Input = OpenDialogMsg;
|
||||||
|
type Output = OpenDialogResponse<S>;
|
||||||
|
|
||||||
|
view! {
|
||||||
|
gtk::FileChooserNative {
|
||||||
|
set_action: if settings.folder_mode {
|
||||||
|
gtk::FileChooserAction::SelectFolder
|
||||||
|
} else {
|
||||||
|
gtk::FileChooserAction::Open
|
||||||
|
},
|
||||||
|
|
||||||
|
set_select_multiple: S::SELECT_MULTIPLE,
|
||||||
|
set_create_folders: settings.create_folders,
|
||||||
|
set_modal: settings.is_modal,
|
||||||
|
set_accept_label: Some(&settings.accept_label),
|
||||||
|
set_cancel_label: Some(&settings.cancel_label),
|
||||||
|
#[iterate]
|
||||||
|
add_filter: &settings.filters,
|
||||||
|
|
||||||
|
#[watch]
|
||||||
|
set_visible: model.visible,
|
||||||
|
|
||||||
|
connect_response[sender] => move |dialog, res_ty| {
|
||||||
|
match res_ty {
|
||||||
|
gtk::ResponseType::Accept => {
|
||||||
|
let selection = S::select(dialog);
|
||||||
|
sender.output(OpenDialogResponse::Accept(selection)).unwrap();
|
||||||
|
}
|
||||||
|
_ => sender.output(OpenDialogResponse::Cancel).unwrap(),
|
||||||
|
}
|
||||||
|
|
||||||
|
sender.input(OpenDialogMsg::Hide);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
settings: Self::Init,
|
||||||
|
root: &Self::Root,
|
||||||
|
sender: ComponentSender<Self>,
|
||||||
|
) -> ComponentParts<Self> {
|
||||||
|
let model = OpenDialogInner {
|
||||||
|
visible: false,
|
||||||
|
_phantom: PhantomData,
|
||||||
|
};
|
||||||
|
|
||||||
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
ComponentParts { model, widgets }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) {
|
||||||
|
match message {
|
||||||
|
OpenDialogMsg::Open => {
|
||||||
|
self.visible = true;
|
||||||
|
}
|
||||||
|
OpenDialogMsg::Hide => {
|
||||||
|
self.visible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
136
Relm4-0.6.2/relm4-components/src/save_dialog.rs
Normal file
136
Relm4-0.6.2/relm4-components/src/save_dialog.rs
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
//! Reusable and easily configurable save dialog component.
|
||||||
|
//!
|
||||||
|
//! **[Example implementation](https://github.com/Relm4/Relm4/blob/main/relm4-components/examples/file_dialogs.rs)**
|
||||||
|
use gtk::prelude::{FileChooserExt, FileExt, NativeDialogExt};
|
||||||
|
use relm4::{gtk, ComponentParts, ComponentSender, SimpleComponent};
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
/// Configuration for the save dialog component
|
||||||
|
pub struct SaveDialogSettings {
|
||||||
|
/// Label for cancel button
|
||||||
|
pub cancel_label: String,
|
||||||
|
/// Label for accept button
|
||||||
|
pub accept_label: String,
|
||||||
|
/// Allow or disallow creating folders
|
||||||
|
pub create_folders: bool,
|
||||||
|
/// Freeze other windows while the dialog is open
|
||||||
|
pub is_modal: bool,
|
||||||
|
/// Filter for MIME types or other patterns
|
||||||
|
pub filters: Vec<gtk::FileFilter>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SaveDialogSettings {
|
||||||
|
fn default() -> Self {
|
||||||
|
SaveDialogSettings {
|
||||||
|
accept_label: String::from("Save"),
|
||||||
|
cancel_label: String::from("Cancel"),
|
||||||
|
create_folders: true,
|
||||||
|
is_modal: true,
|
||||||
|
filters: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// A model for the save dialog component
|
||||||
|
pub struct SaveDialog {
|
||||||
|
current_name: String,
|
||||||
|
visible: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Messages that can be sent to the save dialog component
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum SaveDialogMsg {
|
||||||
|
/// Show the dialog
|
||||||
|
Save,
|
||||||
|
/// Show the dialog, with a suggested file name
|
||||||
|
SaveAs(String),
|
||||||
|
#[doc(hidden)]
|
||||||
|
Hide,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Messages that can be sent from the save dialog component
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum SaveDialogResponse {
|
||||||
|
/// User clicked accept button.
|
||||||
|
Accept(PathBuf),
|
||||||
|
/// User clicked cancel button.
|
||||||
|
Cancel,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Widgets of the save dialog component.
|
||||||
|
#[relm4::component(pub)]
|
||||||
|
impl SimpleComponent for SaveDialog {
|
||||||
|
type Init = SaveDialogSettings;
|
||||||
|
|
||||||
|
type Input = SaveDialogMsg;
|
||||||
|
type Output = SaveDialogResponse;
|
||||||
|
|
||||||
|
view! {
|
||||||
|
gtk::FileChooserNative {
|
||||||
|
set_action: gtk::FileChooserAction::Save,
|
||||||
|
|
||||||
|
set_create_folders: settings.create_folders,
|
||||||
|
set_modal: settings.is_modal,
|
||||||
|
set_accept_label: Some(&settings.accept_label),
|
||||||
|
set_cancel_label: Some(&settings.cancel_label),
|
||||||
|
#[iterate]
|
||||||
|
add_filter: &settings.filters,
|
||||||
|
|
||||||
|
#[watch]
|
||||||
|
set_current_name: &model.current_name,
|
||||||
|
#[watch]
|
||||||
|
set_visible: model.visible,
|
||||||
|
|
||||||
|
connect_response[sender] => move |dialog, res_ty| {
|
||||||
|
match res_ty {
|
||||||
|
gtk::ResponseType::Accept => {
|
||||||
|
if let Some(file) = dialog.file() {
|
||||||
|
if let Some(path) = file.path() {
|
||||||
|
sender.output(SaveDialogResponse::Accept(path)).unwrap();
|
||||||
|
sender.input(SaveDialogMsg::Hide);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sender.output(SaveDialogResponse::Cancel).unwrap();
|
||||||
|
}
|
||||||
|
_ => sender.output(SaveDialogResponse::Cancel).unwrap(),
|
||||||
|
}
|
||||||
|
sender.input(SaveDialogMsg::Hide);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
settings: Self::Init,
|
||||||
|
root: &Self::Root,
|
||||||
|
sender: ComponentSender<Self>,
|
||||||
|
) -> ComponentParts<Self> {
|
||||||
|
let model = SaveDialog {
|
||||||
|
current_name: String::new(),
|
||||||
|
visible: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
ComponentParts { model, widgets }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) {
|
||||||
|
match message {
|
||||||
|
SaveDialogMsg::Save => {
|
||||||
|
self.current_name = String::new();
|
||||||
|
self.visible = true;
|
||||||
|
}
|
||||||
|
SaveDialogMsg::SaveAs(file_name) => {
|
||||||
|
self.current_name = file_name;
|
||||||
|
self.visible = true;
|
||||||
|
}
|
||||||
|
SaveDialogMsg::Hide => {
|
||||||
|
self.visible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
117
Relm4-0.6.2/relm4-components/src/simple_combo_box.rs
Normal file
117
Relm4-0.6.2/relm4-components/src/simple_combo_box.rs
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
//! A wrapper around [`gtk::ComboBoxText`] that makes it easier to use
|
||||||
|
//! from regular Rust code.
|
||||||
|
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
use relm4::gtk::prelude::{ComboBoxExt, ComboBoxExtManual};
|
||||||
|
use relm4::{gtk, ComponentSender};
|
||||||
|
use relm4::{Component, ComponentParts};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
/// A simple wrapper around [`gtk::ComboBox`].
|
||||||
|
///
|
||||||
|
/// This can be used with enums, [`String`]s or any custom type you want.
|
||||||
|
/// The only requirement is that the inner type implements [`ToString`] and [`Debug`].
|
||||||
|
///
|
||||||
|
/// To get notified when the selection changed, you can use
|
||||||
|
/// [`Connector::forward()`](relm4::component::Connector::forward())
|
||||||
|
/// after launching the component.
|
||||||
|
pub struct SimpleComboBox<E: ToString> {
|
||||||
|
/// The variants that can be selected.
|
||||||
|
pub variants: Vec<E>,
|
||||||
|
/// The index of the active element or [`None`] is nothing is selected.
|
||||||
|
pub active_index: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
/// The message type of [`SimpleComboBox`].
|
||||||
|
pub enum SimpleComboBoxMsg<E: ToString> {
|
||||||
|
/// Overwrite the current values.
|
||||||
|
UpdateData(SimpleComboBox<E>),
|
||||||
|
/// Set the index of the active element.
|
||||||
|
SetActiveIdx(usize),
|
||||||
|
#[doc(hidden)]
|
||||||
|
UpdateIndex(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E> Component for SimpleComboBox<E>
|
||||||
|
where
|
||||||
|
E: ToString + 'static + Debug,
|
||||||
|
{
|
||||||
|
type CommandOutput = ();
|
||||||
|
type Input = SimpleComboBoxMsg<E>;
|
||||||
|
type Output = usize;
|
||||||
|
type Init = Self;
|
||||||
|
type Root = gtk::ComboBoxText;
|
||||||
|
type Widgets = gtk::ComboBoxText;
|
||||||
|
|
||||||
|
fn init_root() -> Self::Root {
|
||||||
|
gtk::ComboBoxText::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
model: Self::Init,
|
||||||
|
root: &Self::Root,
|
||||||
|
sender: ComponentSender<Self>,
|
||||||
|
) -> ComponentParts<Self> {
|
||||||
|
let widgets = root.clone();
|
||||||
|
|
||||||
|
model.render(&widgets);
|
||||||
|
|
||||||
|
widgets.connect_changed(move |combo_box| {
|
||||||
|
if let Some(active_idx) = combo_box.active() {
|
||||||
|
sender.input(Self::Input::UpdateIndex(active_idx as usize));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ComponentParts { model, widgets }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_with_view(
|
||||||
|
&mut self,
|
||||||
|
widgets: &mut Self::Widgets,
|
||||||
|
input: Self::Input,
|
||||||
|
sender: ComponentSender<Self>,
|
||||||
|
_root: &Self::Root,
|
||||||
|
) {
|
||||||
|
match input {
|
||||||
|
SimpleComboBoxMsg::UpdateIndex(idx) => {
|
||||||
|
// Ignore send errors because the component might
|
||||||
|
// be detached.
|
||||||
|
sender.output(idx).ok();
|
||||||
|
self.active_index = Some(idx);
|
||||||
|
}
|
||||||
|
SimpleComboBoxMsg::SetActiveIdx(idx) => {
|
||||||
|
if idx < self.variants.len() {
|
||||||
|
self.active_index = Some(idx);
|
||||||
|
widgets.set_active(u32::try_from(idx).ok());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SimpleComboBoxMsg::UpdateData(data) => {
|
||||||
|
*self = data;
|
||||||
|
self.render(widgets);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E> SimpleComboBox<E>
|
||||||
|
where
|
||||||
|
E: ToString,
|
||||||
|
{
|
||||||
|
fn render(&self, combo_box: >k::ComboBoxText) {
|
||||||
|
combo_box.remove_all();
|
||||||
|
|
||||||
|
for (idx, e) in self.variants.iter().enumerate() {
|
||||||
|
combo_box.insert_text(idx as i32, &e.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
combo_box.set_active(self.active_index.and_then(|val| u32::try_from(val).ok()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the value of the currently selected element or [`None`] if nothing is selected.
|
||||||
|
#[must_use]
|
||||||
|
pub fn get_active_elem(&self) -> Option<&E> {
|
||||||
|
self.active_index.map(|idx| &self.variants[idx])
|
||||||
|
}
|
||||||
|
}
|
||||||
143
Relm4-0.6.2/relm4-components/src/web_image.rs
Normal file
143
Relm4-0.6.2/relm4-components/src/web_image.rs
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
//! Reusable and easily configurable component for loading images from the web.
|
||||||
|
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
use relm4::gtk::prelude::{BoxExt, Cast, WidgetExt};
|
||||||
|
use relm4::{gtk, Component, ComponentParts, ComponentSender};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
/// Reusable component for loading images from the web.
|
||||||
|
pub struct WebImage {
|
||||||
|
current_id: usize,
|
||||||
|
current_widget: gtk::Widget,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
/// Load or unload a web image.
|
||||||
|
pub enum WebImageMsg {
|
||||||
|
/// Load an image from an url.
|
||||||
|
LoadImage(String),
|
||||||
|
/// Unload the current image.
|
||||||
|
Unload,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for WebImage {
|
||||||
|
type CommandOutput = Option<(usize, VecDeque<u8>)>;
|
||||||
|
type Input = WebImageMsg;
|
||||||
|
type Output = ();
|
||||||
|
type Init = String;
|
||||||
|
type Root = gtk::Box;
|
||||||
|
type Widgets = ();
|
||||||
|
|
||||||
|
fn init_root() -> Self::Root {
|
||||||
|
gtk::Box::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
url: Self::Init,
|
||||||
|
root: &Self::Root,
|
||||||
|
sender: ComponentSender<Self>,
|
||||||
|
) -> ComponentParts<Self> {
|
||||||
|
let widget = gtk::Box::default();
|
||||||
|
root.append(&widget);
|
||||||
|
let current_widget = Self::set_spinner(root, widget.upcast_ref());
|
||||||
|
|
||||||
|
let model = Self {
|
||||||
|
current_id: 0,
|
||||||
|
current_widget,
|
||||||
|
};
|
||||||
|
|
||||||
|
model.load_image(&sender, url);
|
||||||
|
|
||||||
|
ComponentParts { model, widgets: () }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, input: Self::Input, sender: ComponentSender<Self>, root: &Self::Root) {
|
||||||
|
self.current_widget = Self::set_spinner(root, &self.current_widget);
|
||||||
|
self.current_id = self.current_id.wrapping_add(1);
|
||||||
|
|
||||||
|
match input {
|
||||||
|
WebImageMsg::LoadImage(url) => {
|
||||||
|
self.load_image(&sender, url);
|
||||||
|
}
|
||||||
|
WebImageMsg::Unload => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_cmd(
|
||||||
|
&mut self,
|
||||||
|
message: Self::CommandOutput,
|
||||||
|
sender: ComponentSender<Self>,
|
||||||
|
root: &Self::Root,
|
||||||
|
) {
|
||||||
|
if let Some((id, data)) = message {
|
||||||
|
if id == self.current_id {
|
||||||
|
if let Some(img) = Self::generate_image(data) {
|
||||||
|
self.current_widget = Self::set_image(root, &self.current_widget, &img);
|
||||||
|
sender.output(()).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WebImage {
|
||||||
|
#[must_use]
|
||||||
|
fn set_spinner(root: &<Self as Component>::Root, widget: >k::Widget) -> gtk::Widget {
|
||||||
|
root.remove(widget);
|
||||||
|
relm4::view! {
|
||||||
|
#[local_ref]
|
||||||
|
root -> gtk::Box {
|
||||||
|
set_halign: gtk::Align::Center,
|
||||||
|
set_valign: gtk::Align::Center,
|
||||||
|
|
||||||
|
#[name(spinner)]
|
||||||
|
gtk::Spinner {
|
||||||
|
start: (),
|
||||||
|
set_hexpand: true,
|
||||||
|
set_vexpand: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spinner.upcast()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
fn set_image(
|
||||||
|
root: &<Self as Component>::Root,
|
||||||
|
widget: >k::Widget,
|
||||||
|
img: >k::Image,
|
||||||
|
) -> gtk::Widget {
|
||||||
|
root.remove(widget);
|
||||||
|
relm4::view! {
|
||||||
|
#[local_ref]
|
||||||
|
root -> gtk::Box {
|
||||||
|
set_halign: gtk::Align::Fill,
|
||||||
|
set_valign: gtk::Align::Fill,
|
||||||
|
|
||||||
|
#[local_ref]
|
||||||
|
img -> gtk::Image {
|
||||||
|
set_hexpand: true,
|
||||||
|
set_vexpand: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
img.clone().upcast()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_image(&self, sender: &ComponentSender<Self>, url: String) {
|
||||||
|
sender.oneshot_command(Self::get_img_data(self.current_id, url));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_image(data: VecDeque<u8>) -> Option<gtk::Image> {
|
||||||
|
let pixbuf = gtk::gdk_pixbuf::Pixbuf::from_read(data).ok()?;
|
||||||
|
Some(gtk::Image::from_pixbuf(Some(&pixbuf)))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_img_data(id: usize, url: String) -> Option<(usize, VecDeque<u8>)> {
|
||||||
|
let response = reqwest::get(url).await.ok()?;
|
||||||
|
let bytes = response.bytes().await.ok()?;
|
||||||
|
Some((id, bytes.into_iter().collect()))
|
||||||
|
}
|
||||||
|
}
|
||||||
43
Relm4-0.6.2/relm4-macros/Cargo.toml
Normal file
43
Relm4-0.6.2/relm4-macros/Cargo.toml
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
[package]
|
||||||
|
name = "relm4-macros"
|
||||||
|
readme = "README.md"
|
||||||
|
keywords = ["gui", "gtk", "gtk4", "elm", "view"]
|
||||||
|
documentation = "https://docs.rs/relm4_macros/"
|
||||||
|
|
||||||
|
version.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
description.workspace = true
|
||||||
|
|
||||||
|
homepage.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
|
||||||
|
categories.workspace = true
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["relm4"]
|
||||||
|
|
||||||
|
# Without the default "relm4" feature, all imports of gtk will
|
||||||
|
# be `use gtk;` instead of `use relm4::gtk;` thus making it
|
||||||
|
# easier to use this crate without Relm4.
|
||||||
|
relm4 = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
proc-macro2 = "1.0"
|
||||||
|
quote = "1.0"
|
||||||
|
syn = { version = "2.0", features = [
|
||||||
|
"full",
|
||||||
|
"extra-traits",
|
||||||
|
"visit",
|
||||||
|
"visit-mut",
|
||||||
|
] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
relm4 = { path = "../relm4" }
|
||||||
|
rustversion = "1"
|
||||||
|
trybuild = "1.0.80"
|
||||||
6
Relm4-0.6.2/relm4-macros/README.md
Normal file
6
Relm4-0.6.2/relm4-macros/README.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# Relm4-macros
|
||||||
|
|
||||||
|
[](https://crates.io/crates/relm4-macros)
|
||||||
|
[](https://docs.rs/relm4_macros/)
|
||||||
|
|
||||||
|
A macro to easily generate UIs for Relm4 applications.
|
||||||
24
Relm4-0.6.2/relm4-macros/src/additional_fields.rs
Normal file
24
Relm4-0.6.2/relm4-macros/src/additional_fields.rs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::ToTokens;
|
||||||
|
use syn::parse::{Parse, ParseStream};
|
||||||
|
use syn::punctuated::Punctuated;
|
||||||
|
use syn::{Field, Result, Token};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(super) struct AdditionalFields {
|
||||||
|
pub(super) inner: Punctuated<Field, Token![,]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for AdditionalFields {
|
||||||
|
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||||
|
Ok(AdditionalFields {
|
||||||
|
inner: input.parse_terminated(Field::parse_named, Token![,])?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for AdditionalFields {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
|
tokens.extend(self.inner.to_token_stream());
|
||||||
|
}
|
||||||
|
}
|
||||||
49
Relm4-0.6.2/relm4-macros/src/args.rs
Normal file
49
Relm4-0.6.2/relm4-macros/src/args.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::{quote_spanned, ToTokens};
|
||||||
|
use syn::parse::{Parse, ParseStream};
|
||||||
|
use syn::punctuated::{Pair, Punctuated};
|
||||||
|
use syn::spanned::Spanned;
|
||||||
|
use syn::{Error, Result, Token};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(super) struct Args<T>
|
||||||
|
where
|
||||||
|
T: Parse + ToTokens,
|
||||||
|
{
|
||||||
|
pub(super) inner: Vec<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Parse for Args<T>
|
||||||
|
where
|
||||||
|
T: Parse + ToTokens,
|
||||||
|
{
|
||||||
|
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||||
|
let punct: Punctuated<T, Token![,]> = input.call(Punctuated::parse_terminated)?;
|
||||||
|
if punct.is_empty() {
|
||||||
|
return Err(Error::new(input.span(), "Expected at least one element. This is probably caused by empty arguments and macros."));
|
||||||
|
}
|
||||||
|
let inner = punct.into_pairs().map(Pair::into_value).collect();
|
||||||
|
|
||||||
|
Ok(Args { inner })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> ToTokens for Args<T>
|
||||||
|
where
|
||||||
|
T: Parse + ToTokens,
|
||||||
|
{
|
||||||
|
fn to_tokens(&self, out: &mut TokenStream2) {
|
||||||
|
let mut iter = self.inner.iter();
|
||||||
|
|
||||||
|
let first = iter.next().unwrap();
|
||||||
|
out.extend(quote_spanned! {
|
||||||
|
first.span() => #first
|
||||||
|
});
|
||||||
|
|
||||||
|
for expr in iter {
|
||||||
|
out.extend(quote_spanned! {
|
||||||
|
expr.span() => ,#expr
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
77
Relm4-0.6.2/relm4-macros/src/attrs.rs
Normal file
77
Relm4-0.6.2/relm4-macros/src/attrs.rs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
use syn::parse::{Parse, ParseStream};
|
||||||
|
use syn::spanned::Spanned;
|
||||||
|
use syn::token::Async;
|
||||||
|
use syn::{Error, Result, Token, Visibility};
|
||||||
|
|
||||||
|
pub(super) struct Attrs {
|
||||||
|
/// Keeps information about visibility of the widget
|
||||||
|
pub(super) visibility: Option<Visibility>,
|
||||||
|
/// Whether an async trait is used or not
|
||||||
|
pub(super) asyncness: Option<Async>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct SyncOnlyAttrs {
|
||||||
|
/// Keeps information about visibility of the widget
|
||||||
|
pub(super) visibility: Option<Visibility>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for SyncOnlyAttrs {
|
||||||
|
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||||
|
let Attrs {
|
||||||
|
visibility,
|
||||||
|
asyncness,
|
||||||
|
} = input.parse()?;
|
||||||
|
|
||||||
|
if let Some(async_token) = asyncness {
|
||||||
|
Err(syn::Error::new(
|
||||||
|
async_token.span,
|
||||||
|
"this macro doesn't support async traits",
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(Self { visibility })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Attrs {
|
||||||
|
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||||
|
let mut attrs = Attrs {
|
||||||
|
visibility: None,
|
||||||
|
asyncness: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
while !input.is_empty() {
|
||||||
|
if input.peek(Async) {
|
||||||
|
let new_asyncness: Async = input.parse()?;
|
||||||
|
if attrs.asyncness.is_some() {
|
||||||
|
return Err(syn::Error::new(
|
||||||
|
new_asyncness.span,
|
||||||
|
"cannot specify asyncness twice",
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
attrs.asyncness = Some(new_asyncness);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let new_vis: Visibility = input.parse()?;
|
||||||
|
if attrs.visibility.is_some() {
|
||||||
|
return Err(syn::Error::new(
|
||||||
|
new_vis.span(),
|
||||||
|
"cannot specify visibility twice",
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
attrs.visibility = Some(new_vis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.peek(Token![,]) {
|
||||||
|
let comma: Token![,] = input.parse()?;
|
||||||
|
if input.is_empty() {
|
||||||
|
// We've just consumed last token in stream (which is comma) and that's wrong
|
||||||
|
return Err(Error::new(comma.span, "expected visibility or `async`"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(attrs)
|
||||||
|
}
|
||||||
|
}
|
||||||
192
Relm4-0.6.2/relm4-macros/src/component/mod.rs
Normal file
192
Relm4-0.6.2/relm4-macros/src/component/mod.rs
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::{quote, quote_spanned, ToTokens};
|
||||||
|
use syn::parse_quote;
|
||||||
|
use syn::visit_mut::VisitMut;
|
||||||
|
|
||||||
|
use crate::attrs::Attrs;
|
||||||
|
use crate::token_streams::{TokenStreams, TraitImplDetails};
|
||||||
|
use crate::util;
|
||||||
|
use crate::visitors::{ComponentVisitor, PreAndPostView, ViewOutputExpander};
|
||||||
|
|
||||||
|
pub(crate) fn generate_tokens(
|
||||||
|
global_attributes: Attrs,
|
||||||
|
mut component_impl: syn::ItemImpl,
|
||||||
|
) -> TokenStream2 {
|
||||||
|
let Attrs {
|
||||||
|
visibility,
|
||||||
|
asyncness,
|
||||||
|
} = global_attributes;
|
||||||
|
|
||||||
|
let mut errors = vec![];
|
||||||
|
|
||||||
|
let mut component_visitor = ComponentVisitor::new(&mut errors);
|
||||||
|
|
||||||
|
component_visitor.visit_item_impl_mut(&mut component_impl);
|
||||||
|
|
||||||
|
let additional_fields = component_visitor.additional_fields.take();
|
||||||
|
|
||||||
|
let menus_stream = component_visitor
|
||||||
|
.menus
|
||||||
|
.take()
|
||||||
|
.map(|menus| menus.menus_stream());
|
||||||
|
|
||||||
|
let mut struct_fields = None;
|
||||||
|
|
||||||
|
match &component_visitor.view_widgets {
|
||||||
|
None => component_visitor.errors.push(syn::Error::new_spanned(
|
||||||
|
&component_impl,
|
||||||
|
"expected `view!` macro invocation",
|
||||||
|
)),
|
||||||
|
Some(Err(e)) => component_visitor.errors.push(e.clone()),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
if let ComponentVisitor {
|
||||||
|
view_widgets: Some(Ok(view_widgets)),
|
||||||
|
model_name: Some(model_name),
|
||||||
|
root_name: Some(root_name),
|
||||||
|
sender_name: Some(sender_name),
|
||||||
|
errors,
|
||||||
|
..
|
||||||
|
} = component_visitor
|
||||||
|
{
|
||||||
|
let trait_impl_details = TraitImplDetails {
|
||||||
|
vis: visibility.clone(),
|
||||||
|
model_name,
|
||||||
|
sender_name,
|
||||||
|
root_name: Some(root_name),
|
||||||
|
};
|
||||||
|
|
||||||
|
let TokenStreams {
|
||||||
|
error,
|
||||||
|
init_root,
|
||||||
|
rename_root,
|
||||||
|
struct_fields: struct_fields_stream,
|
||||||
|
init: init_widgets,
|
||||||
|
assign,
|
||||||
|
return_fields,
|
||||||
|
destructure_fields,
|
||||||
|
update_view,
|
||||||
|
} = view_widgets.generate_streams(&trait_impl_details, false);
|
||||||
|
|
||||||
|
let model_name = trait_impl_details.model_name;
|
||||||
|
|
||||||
|
struct_fields = Some(struct_fields_stream);
|
||||||
|
let root_widget_type = view_widgets.root_type();
|
||||||
|
|
||||||
|
// Extract identifiers from additional fields for struct initialization: "test: u8" => "test"
|
||||||
|
let additional_fields_return_stream = if let Some(fields) = &additional_fields {
|
||||||
|
let mut tokens = TokenStream2::new();
|
||||||
|
for field in fields.inner.pairs() {
|
||||||
|
tokens.extend(field.value().ident.to_token_stream());
|
||||||
|
tokens.extend(quote! {,});
|
||||||
|
}
|
||||||
|
tokens
|
||||||
|
} else {
|
||||||
|
TokenStream2::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
let view_code = quote! {
|
||||||
|
#rename_root
|
||||||
|
#menus_stream
|
||||||
|
#init_widgets
|
||||||
|
#assign
|
||||||
|
{
|
||||||
|
#error
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let widgets_return_code = parse_quote! {
|
||||||
|
Self::Widgets {
|
||||||
|
#return_fields
|
||||||
|
#additional_fields_return_stream
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ViewOutputExpander::expand(&mut component_impl, view_code, widgets_return_code, errors);
|
||||||
|
|
||||||
|
component_impl.items.push(parse_quote! {
|
||||||
|
type Root = #root_widget_type;
|
||||||
|
});
|
||||||
|
|
||||||
|
let ty: syn::Type = parse_quote!(Self::Root);
|
||||||
|
let init_root = util::verbatim_impl_item_fn("init_root", Vec::new(), ty, init_root);
|
||||||
|
component_impl.items.push(init_root);
|
||||||
|
|
||||||
|
let PreAndPostView {
|
||||||
|
pre_view,
|
||||||
|
post_view,
|
||||||
|
..
|
||||||
|
} = PreAndPostView::extract(&mut component_impl, errors);
|
||||||
|
|
||||||
|
let sender_ty: syn::TypePath = if asyncness.is_some() {
|
||||||
|
parse_quote! { relm4::AsyncComponentSender }
|
||||||
|
} else {
|
||||||
|
parse_quote! { relm4::ComponentSender }
|
||||||
|
};
|
||||||
|
|
||||||
|
component_impl.items.push(parse_quote! {
|
||||||
|
/// Update the view to represent the updated model.
|
||||||
|
fn update_view(
|
||||||
|
&self,
|
||||||
|
widgets: &mut Self::Widgets,
|
||||||
|
sender: #sender_ty<Self>,
|
||||||
|
) {
|
||||||
|
struct __DoNotReturnManually;
|
||||||
|
|
||||||
|
let _no_manual_return: __DoNotReturnManually = (move || {
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
let Self::Widgets {
|
||||||
|
#destructure_fields
|
||||||
|
#additional_fields_return_stream
|
||||||
|
} = widgets;
|
||||||
|
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
let #model_name = self;
|
||||||
|
|
||||||
|
#(#pre_view)*
|
||||||
|
#update_view
|
||||||
|
// In post_view returning early is ok
|
||||||
|
(move || { #(#post_view)* })();
|
||||||
|
|
||||||
|
__DoNotReturnManually
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the widget type if used.
|
||||||
|
let widgets_name = util::generate_widgets_type(
|
||||||
|
component_visitor.widgets_ty,
|
||||||
|
&mut component_impl,
|
||||||
|
&mut errors,
|
||||||
|
);
|
||||||
|
|
||||||
|
let widgets_struct = widgets_name.map(|widgets_name| {
|
||||||
|
let outer_attrs = &component_impl.attrs;
|
||||||
|
quote! {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#(#outer_attrs)*
|
||||||
|
#[derive(Debug)]
|
||||||
|
#visibility struct #widgets_name {
|
||||||
|
#struct_fields
|
||||||
|
#additional_fields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let errors = errors.iter().map(syn::Error::to_compile_error);
|
||||||
|
|
||||||
|
let async_trait = asyncness.map(
|
||||||
|
|async_token| quote_spanned!(async_token.span => #[relm4::async_trait::async_trait(?Send)]),
|
||||||
|
);
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#widgets_struct
|
||||||
|
|
||||||
|
#async_trait
|
||||||
|
#component_impl
|
||||||
|
|
||||||
|
#(#errors)*
|
||||||
|
}
|
||||||
|
}
|
||||||
216
Relm4-0.6.2/relm4-macros/src/factory/mod.rs
Normal file
216
Relm4-0.6.2/relm4-macros/src/factory/mod.rs
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
use proc_macro2::{Span as Span2, TokenStream as TokenStream2};
|
||||||
|
use quote::{quote, quote_spanned, ToTokens};
|
||||||
|
use syn::visit_mut::VisitMut;
|
||||||
|
use syn::{parse_quote, Ident};
|
||||||
|
|
||||||
|
use crate::attrs::Attrs;
|
||||||
|
use crate::token_streams::{TokenStreams, TraitImplDetails};
|
||||||
|
use crate::util;
|
||||||
|
use crate::visitors::{FactoryComponentVisitor, PreAndPostView, ViewOutputExpander};
|
||||||
|
|
||||||
|
pub(crate) fn generate_tokens(
|
||||||
|
global_attributes: Attrs,
|
||||||
|
mut factory_impl: syn::ItemImpl,
|
||||||
|
) -> TokenStream2 {
|
||||||
|
let Attrs {
|
||||||
|
visibility,
|
||||||
|
asyncness,
|
||||||
|
} = global_attributes;
|
||||||
|
|
||||||
|
let mut errors = vec![];
|
||||||
|
|
||||||
|
let mut factory_visitor = FactoryComponentVisitor::new(&mut errors);
|
||||||
|
factory_visitor.visit_item_impl_mut(&mut factory_impl);
|
||||||
|
|
||||||
|
let additional_fields = factory_visitor.additional_fields.take();
|
||||||
|
|
||||||
|
let menus_stream = factory_visitor.menus.take().map(|m| m.menus_stream());
|
||||||
|
|
||||||
|
let mut struct_fields = None;
|
||||||
|
|
||||||
|
match &factory_visitor.view_widgets {
|
||||||
|
None => factory_visitor.errors.push(syn::Error::new_spanned(
|
||||||
|
&factory_impl,
|
||||||
|
"expected `view!` macro invocation",
|
||||||
|
)),
|
||||||
|
Some(Err(e)) => factory_visitor.errors.push(e.clone()),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert default index type for sync variants
|
||||||
|
// if it wasn't specified by the user.
|
||||||
|
if factory_visitor.index_ty.is_none() && asyncness.is_none() {
|
||||||
|
factory_impl.items.push(parse_quote! {
|
||||||
|
type Index = relm4::factory::DynamicIndex;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if let FactoryComponentVisitor {
|
||||||
|
view_widgets: Some(Ok(view_widgets)),
|
||||||
|
root_name,
|
||||||
|
init_widgets,
|
||||||
|
errors,
|
||||||
|
..
|
||||||
|
} = factory_visitor
|
||||||
|
{
|
||||||
|
let TokenStreams {
|
||||||
|
error,
|
||||||
|
init_root,
|
||||||
|
rename_root,
|
||||||
|
struct_fields: struct_fields_stream,
|
||||||
|
init,
|
||||||
|
assign,
|
||||||
|
return_fields,
|
||||||
|
destructure_fields,
|
||||||
|
update_view,
|
||||||
|
} = view_widgets.generate_streams(
|
||||||
|
&TraitImplDetails {
|
||||||
|
vis: visibility.clone(),
|
||||||
|
model_name: Ident::new("self", Span2::call_site()),
|
||||||
|
root_name: Some(
|
||||||
|
root_name.unwrap_or_else(|| Ident::new("root", Span2::call_site())),
|
||||||
|
),
|
||||||
|
sender_name: Ident::new("sender", Span2::call_site()),
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
struct_fields = Some(struct_fields_stream);
|
||||||
|
|
||||||
|
let root_widget_type = view_widgets.root_type();
|
||||||
|
|
||||||
|
// Extract identifiers from additional fields for struct initialization: "test: u8" => "test"
|
||||||
|
let additional_fields_return_stream = if let Some(fields) = &additional_fields {
|
||||||
|
let mut tokens = TokenStream2::new();
|
||||||
|
for field in fields.inner.pairs() {
|
||||||
|
tokens.extend(field.value().ident.to_token_stream());
|
||||||
|
tokens.extend(quote! {,});
|
||||||
|
}
|
||||||
|
tokens
|
||||||
|
} else {
|
||||||
|
TokenStream2::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
let view_code = quote! {
|
||||||
|
#rename_root
|
||||||
|
#menus_stream
|
||||||
|
#init
|
||||||
|
#assign
|
||||||
|
{
|
||||||
|
#error
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let widgets_return_code = parse_quote! {
|
||||||
|
Self::Widgets {
|
||||||
|
#return_fields
|
||||||
|
#additional_fields_return_stream
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let sender_ty: Ident = if asyncness.is_some() {
|
||||||
|
parse_quote! { AsyncFactorySender }
|
||||||
|
} else {
|
||||||
|
parse_quote! { FactorySender }
|
||||||
|
};
|
||||||
|
|
||||||
|
let index_ty: syn::TypePath = if asyncness.is_some() {
|
||||||
|
parse_quote! { relm4::factory::DynamicIndex }
|
||||||
|
} else {
|
||||||
|
parse_quote! { Self::Index }
|
||||||
|
};
|
||||||
|
|
||||||
|
if init_widgets.is_some() {
|
||||||
|
ViewOutputExpander::expand(&mut factory_impl, view_code, widgets_return_code, errors);
|
||||||
|
} else {
|
||||||
|
factory_impl.items.push(parse_quote! {
|
||||||
|
fn init_widgets(
|
||||||
|
&mut self,
|
||||||
|
index: & #index_ty,
|
||||||
|
root: &Self::Root,
|
||||||
|
returned_widget: &<Self::ParentWidget as relm4::factory::FactoryView>::ReturnedWidget,
|
||||||
|
sender: relm4::factory::#sender_ty<Self>,
|
||||||
|
) -> Self::Widgets {
|
||||||
|
#view_code
|
||||||
|
#widgets_return_code
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
factory_impl.items.push(parse_quote! {
|
||||||
|
type Root = #root_widget_type;
|
||||||
|
});
|
||||||
|
|
||||||
|
let ty: syn::Type = parse_quote!(Self::Root);
|
||||||
|
factory_impl.items.push(if asyncness.is_some() {
|
||||||
|
util::verbatim_impl_item_fn("init_root", Vec::new(), ty, init_root)
|
||||||
|
} else {
|
||||||
|
let args = vec![parse_quote! { &self}];
|
||||||
|
util::verbatim_impl_item_fn("init_root", args, ty, init_root)
|
||||||
|
});
|
||||||
|
|
||||||
|
let PreAndPostView {
|
||||||
|
pre_view,
|
||||||
|
post_view,
|
||||||
|
..
|
||||||
|
} = PreAndPostView::extract(&mut factory_impl, errors);
|
||||||
|
|
||||||
|
factory_impl.items.push(parse_quote! {
|
||||||
|
// Update the view to represent the updated model.
|
||||||
|
fn update_view(
|
||||||
|
&self,
|
||||||
|
widgets: &mut Self::Widgets,
|
||||||
|
sender: relm4::factory::#sender_ty<Self>,
|
||||||
|
) {
|
||||||
|
struct __DoNotReturnManually;
|
||||||
|
|
||||||
|
let _no_manual_return: __DoNotReturnManually = (move || {
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
let Self::Widgets {
|
||||||
|
#destructure_fields
|
||||||
|
#additional_fields_return_stream
|
||||||
|
} = widgets;
|
||||||
|
|
||||||
|
#(#pre_view)*
|
||||||
|
#update_view
|
||||||
|
// In post_view returning early is ok
|
||||||
|
(move || { #(#post_view)* })();
|
||||||
|
|
||||||
|
__DoNotReturnManually
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the widget type if used.
|
||||||
|
let widgets_name =
|
||||||
|
util::generate_widgets_type(factory_visitor.widgets_ty, &mut factory_impl, &mut errors);
|
||||||
|
|
||||||
|
let widgets_struct = widgets_name.map(|ty| {
|
||||||
|
let outer_attrs = &factory_impl.attrs;
|
||||||
|
quote! {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#(#outer_attrs)*
|
||||||
|
#[derive(Debug)]
|
||||||
|
#visibility struct #ty {
|
||||||
|
#struct_fields
|
||||||
|
#additional_fields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let errors = errors.iter().map(syn::Error::to_compile_error);
|
||||||
|
|
||||||
|
let async_trait = asyncness.map(
|
||||||
|
|async_token| quote_spanned!(async_token.span => #[relm4::async_trait::async_trait(?Send)]),
|
||||||
|
);
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#widgets_struct
|
||||||
|
|
||||||
|
#async_trait
|
||||||
|
#factory_impl
|
||||||
|
|
||||||
|
#(#errors)*
|
||||||
|
}
|
||||||
|
}
|
||||||
648
Relm4-0.6.2/relm4-macros/src/lib.rs
Normal file
648
Relm4-0.6.2/relm4-macros/src/lib.rs
Normal file
@ -0,0 +1,648 @@
|
|||||||
|
//! A collection of macros for gtk-rs, Relm4 and Rust in general.
|
||||||
|
//!
|
||||||
|
//! Docs of related crates:
|
||||||
|
//! [relm4](https://docs.rs/relm4)
|
||||||
|
//! | [relm4-macros](https://docs.rs/relm4_macros)
|
||||||
|
//! | [relm4-components](https://docs.rs/relm4_components)
|
||||||
|
//! | [gtk4-rs](https://gtk-rs.org/gtk4-rs/git/docs)
|
||||||
|
//! | [gtk-rs-core](https://gtk-rs.org/gtk-rs-core/git/docs)
|
||||||
|
//! | [libadwaita-rs](https://world.pages.gitlab.gnome.org/Rust/libadwaita-rs/git/docs/libadwaita)
|
||||||
|
//! | [libpanel-rs](https://world.pages.gitlab.gnome.org/Rust/libpanel-rs/git/docs/libpanel)
|
||||||
|
//!
|
||||||
|
//! [GitHub](https://github.com/Relm4/Relm4)
|
||||||
|
//! | [Website](https://relm4.org)
|
||||||
|
//! | [Book](https://relm4.org/book/stable/)
|
||||||
|
//! | [Blog](https://relm4.org/blog)
|
||||||
|
|
||||||
|
#![doc(html_logo_url = "https://relm4.org/icons/relm4_logo.svg")]
|
||||||
|
#![doc(html_favicon_url = "https://relm4.org/icons/relm4_org.svg")]
|
||||||
|
#![warn(
|
||||||
|
missing_debug_implementations,
|
||||||
|
missing_docs,
|
||||||
|
rust_2018_idioms,
|
||||||
|
unreachable_pub,
|
||||||
|
unused_qualifications,
|
||||||
|
clippy::cargo,
|
||||||
|
clippy::must_use_candidate
|
||||||
|
)]
|
||||||
|
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use syn::{parse_macro_input, ItemImpl};
|
||||||
|
|
||||||
|
mod additional_fields;
|
||||||
|
mod args;
|
||||||
|
mod attrs;
|
||||||
|
mod component;
|
||||||
|
mod menu;
|
||||||
|
mod view;
|
||||||
|
mod visitors;
|
||||||
|
mod widgets;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod util;
|
||||||
|
mod factory;
|
||||||
|
mod token_streams;
|
||||||
|
mod widget_template;
|
||||||
|
|
||||||
|
use attrs::{Attrs, SyncOnlyAttrs};
|
||||||
|
use menu::Menus;
|
||||||
|
|
||||||
|
fn gtk_import() -> syn::Path {
|
||||||
|
if cfg!(feature = "relm4") {
|
||||||
|
util::strings_to_path(&["relm4", "gtk"])
|
||||||
|
} else {
|
||||||
|
util::strings_to_path(&["gtk"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Macro that implements `relm4::Component` or `relm4::SimpleComponent`
|
||||||
|
/// and generates the corresponding widgets struct.
|
||||||
|
///
|
||||||
|
/// # Attributes
|
||||||
|
///
|
||||||
|
/// To create public struct use `#[component(pub)]` or `#[component(visibility = pub)]`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use relm4::prelude::*;
|
||||||
|
/// use gtk::prelude::*;
|
||||||
|
///
|
||||||
|
/// #[derive(Default)]
|
||||||
|
/// struct App {
|
||||||
|
/// counter: u8,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[derive(Debug)]
|
||||||
|
/// enum Msg {
|
||||||
|
/// Increment,
|
||||||
|
/// Decrement,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[relm4_macros::component(pub)]
|
||||||
|
/// impl SimpleComponent for App {
|
||||||
|
/// type Init = u8;
|
||||||
|
/// type Input = Msg;
|
||||||
|
/// type Output = ();
|
||||||
|
///
|
||||||
|
/// view! {
|
||||||
|
/// gtk::Window {
|
||||||
|
/// set_title: Some("Simple app"),
|
||||||
|
/// set_default_size: (300, 100),
|
||||||
|
/// gtk::Box {
|
||||||
|
/// set_orientation: gtk::Orientation::Vertical,
|
||||||
|
/// set_margin_all: 5,
|
||||||
|
/// set_spacing: 5,
|
||||||
|
///
|
||||||
|
/// gtk::Button {
|
||||||
|
/// set_label: "Increment",
|
||||||
|
/// connect_clicked => Msg::Increment,
|
||||||
|
/// },
|
||||||
|
/// gtk::Button {
|
||||||
|
/// set_label: "Decrement",
|
||||||
|
/// connect_clicked[sender] => move |_| {
|
||||||
|
/// sender.input(Msg::Decrement);
|
||||||
|
/// },
|
||||||
|
/// },
|
||||||
|
/// gtk::Label {
|
||||||
|
/// set_margin_all: 5,
|
||||||
|
/// #[watch]
|
||||||
|
/// set_label: &format!("Counter: {}", model.counter),
|
||||||
|
/// }
|
||||||
|
/// },
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn init(
|
||||||
|
/// counter: Self::Init,
|
||||||
|
/// root: &Self::Root,
|
||||||
|
/// sender: ComponentSender<Self>,
|
||||||
|
/// ) -> ComponentParts<Self> {
|
||||||
|
/// let model = Self { counter };
|
||||||
|
///
|
||||||
|
/// let widgets = view_output!();
|
||||||
|
///
|
||||||
|
/// ComponentParts { model, widgets }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn update(&mut self, msg: Msg, _sender: ComponentSender<Self>) {
|
||||||
|
/// match msg {
|
||||||
|
/// Msg::Increment => {
|
||||||
|
/// self.counter = self.counter.wrapping_add(1);
|
||||||
|
/// }
|
||||||
|
/// Msg::Decrement => {
|
||||||
|
/// self.counter = self.counter.wrapping_sub(1);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Notes on `pre_view`
|
||||||
|
///
|
||||||
|
/// Using `return` in `pre_view` will cause a compiler warning.
|
||||||
|
/// In general, you don't want to use `return` in `pre_view` as it will
|
||||||
|
/// cause all following update functionality to be skipped.
|
||||||
|
///
|
||||||
|
/// ```compile_fail
|
||||||
|
/// # use gtk::prelude::{BoxExt, ButtonExt, GtkWindowExt, OrientableExt};
|
||||||
|
/// # use relm4::{gtk, ComponentParts, ComponentSender, SimpleComponent, RelmWidgetExt};
|
||||||
|
/// #
|
||||||
|
/// struct App {}
|
||||||
|
///
|
||||||
|
/// #[relm4_macros::component]
|
||||||
|
/// impl SimpleComponent for App {
|
||||||
|
/// /* Code omitted */
|
||||||
|
/// # type Init = ();
|
||||||
|
/// # type Input = ();
|
||||||
|
/// # type Output = ();
|
||||||
|
/// #
|
||||||
|
/// # view! {
|
||||||
|
/// # gtk::Window {}
|
||||||
|
/// # }
|
||||||
|
///
|
||||||
|
/// fn pre_view() {
|
||||||
|
/// return;
|
||||||
|
/// }
|
||||||
|
/// #
|
||||||
|
/// # fn init(
|
||||||
|
/// # counter: Self::Init,
|
||||||
|
/// # root: &Self::Root,
|
||||||
|
/// # sender: ComponentSender<Self>,
|
||||||
|
/// # ) -> ComponentParts<Self> {
|
||||||
|
/// # let model = Self {};
|
||||||
|
/// #
|
||||||
|
/// # let widgets = view_output!();
|
||||||
|
/// #
|
||||||
|
/// # ComponentParts { model, widgets }
|
||||||
|
/// # }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn component(attributes: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
let global_attributes: Attrs = parse_macro_input!(attributes);
|
||||||
|
let backup_input = input.clone();
|
||||||
|
let component_impl_res = syn::parse::<ItemImpl>(input);
|
||||||
|
|
||||||
|
match component_impl_res {
|
||||||
|
Ok(component_impl) => component::generate_tokens(global_attributes, component_impl).into(),
|
||||||
|
Err(_) => util::item_impl_error(backup_input),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Macro that implements `relm4::factory::FactoryComponent` and generates the corresponding widgets struct.
|
||||||
|
///
|
||||||
|
/// # Attributes
|
||||||
|
///
|
||||||
|
/// To create public struct use `#[factory(pub)]` or `#[factory(visibility = pub)]`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use relm4::prelude::*;
|
||||||
|
/// use relm4::factory::*;
|
||||||
|
/// use gtk::prelude::*;
|
||||||
|
///
|
||||||
|
/// # #[derive(Debug)]
|
||||||
|
/// # enum AppMsg {
|
||||||
|
/// # AddCounter,
|
||||||
|
/// # RemoveCounter,
|
||||||
|
/// # SendFront(DynamicIndex)
|
||||||
|
/// # }
|
||||||
|
///
|
||||||
|
/// #[derive(Debug)]
|
||||||
|
/// struct Counter {
|
||||||
|
/// value: u8,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[derive(Debug)]
|
||||||
|
/// enum CounterMsg {
|
||||||
|
/// Increment,
|
||||||
|
/// Decrement,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[derive(Debug)]
|
||||||
|
/// enum CounterOutput {
|
||||||
|
/// SendFront(DynamicIndex),
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[relm4_macros::factory(pub)]
|
||||||
|
/// impl FactoryComponent for Counter {
|
||||||
|
/// type CommandOutput = ();
|
||||||
|
/// type Init = u8;
|
||||||
|
/// type Input = CounterMsg;
|
||||||
|
/// type Output = CounterOutput;
|
||||||
|
/// type ParentInput = AppMsg;
|
||||||
|
/// type ParentWidget = gtk::Box;
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// view! {
|
||||||
|
/// root = gtk::Box {
|
||||||
|
/// set_orientation: gtk::Orientation::Horizontal,
|
||||||
|
/// set_spacing: 10,
|
||||||
|
///
|
||||||
|
/// #[name(label)]
|
||||||
|
/// gtk::Label {
|
||||||
|
/// #[watch]
|
||||||
|
/// set_label: &self.value.to_string(),
|
||||||
|
/// set_width_chars: 3,
|
||||||
|
/// },
|
||||||
|
///
|
||||||
|
/// #[name(add_button)]
|
||||||
|
/// gtk::Button {
|
||||||
|
/// set_label: "+",
|
||||||
|
/// connect_clicked => CounterMsg::Increment,
|
||||||
|
/// },
|
||||||
|
///
|
||||||
|
/// #[name(remove_button)]
|
||||||
|
/// gtk::Button {
|
||||||
|
/// set_label: "-",
|
||||||
|
/// connect_clicked => CounterMsg::Decrement,
|
||||||
|
/// },
|
||||||
|
///
|
||||||
|
/// #[name(to_front_button)]
|
||||||
|
/// gtk::Button {
|
||||||
|
/// set_label: "To start",
|
||||||
|
/// connect_clicked[sender, index] => move |_| {
|
||||||
|
/// sender.output(CounterOutput::SendFront(index.clone()))
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn forward_to_parent(output: Self::Output) -> Option<AppMsg> {
|
||||||
|
/// Some(match output {
|
||||||
|
/// CounterOutput::SendFront(index) => AppMsg::SendFront(index),
|
||||||
|
/// })
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn init_model(
|
||||||
|
/// value: Self::Init,
|
||||||
|
/// _index: &DynamicIndex,
|
||||||
|
/// _sender: FactorySender<Self>,
|
||||||
|
/// ) -> Self {
|
||||||
|
/// Self { value }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn update(&mut self, msg: Self::Input, _sender: FactorySender<Self>) {
|
||||||
|
/// match msg {
|
||||||
|
/// CounterMsg::Increment => {
|
||||||
|
/// self.value = self.value.wrapping_add(1);
|
||||||
|
/// }
|
||||||
|
/// CounterMsg::Decrement => {
|
||||||
|
/// self.value = self.value.wrapping_sub(1);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn factory(attributes: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
let attrs = parse_macro_input!(attributes);
|
||||||
|
let backup_input = input.clone();
|
||||||
|
let factory_impl_res = syn::parse::<ItemImpl>(input);
|
||||||
|
|
||||||
|
match factory_impl_res {
|
||||||
|
Ok(factory_impl) => factory::generate_tokens(attrs, factory_impl).into(),
|
||||||
|
Err(_) => util::item_impl_error(backup_input),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A macro to create menus.
|
||||||
|
///
|
||||||
|
/// Use
|
||||||
|
///
|
||||||
|
/// + `"Label text" => ActionType,` to create new entries.
|
||||||
|
/// + `"Label text" => ActionType(value),` to create new entries with action value.
|
||||||
|
/// + `custom => "widget_id",` add a placeholder for custom widgets you can add later with [`set_attribute_name`](https://gtk-rs.org/gtk-rs-core/stable/0.15/docs/gio/struct.MenuItem.html#method.set_attribute_value).
|
||||||
|
/// + `section! { ... }` to create new sections.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn gettext(string: &str) -> String {
|
||||||
|
/// # string.to_owned()
|
||||||
|
/// # }
|
||||||
|
/// #
|
||||||
|
/// // Define some actions
|
||||||
|
/// relm4::new_action_group!(WindowActionGroup, "win");
|
||||||
|
/// relm4::new_stateless_action!(TestAction, WindowActionGroup, "test");
|
||||||
|
/// relm4::new_stateful_action!(TestU8Action, WindowActionGroup, "test2", u8, u8);
|
||||||
|
///
|
||||||
|
/// // Create a `MenuModel` called `menu_model`
|
||||||
|
/// relm4_macros::menu! {
|
||||||
|
/// main_menu: {
|
||||||
|
/// custom: "my_widget",
|
||||||
|
/// // Translate with gettext-rs, for example.
|
||||||
|
/// &gettext("Test") => TestAction,
|
||||||
|
/// "Test2" => TestAction,
|
||||||
|
/// "Test toggle" => TestU8Action(1_u8),
|
||||||
|
/// section! {
|
||||||
|
/// "Section test" => TestAction,
|
||||||
|
/// "Test toggle" => TestU8Action(1_u8),
|
||||||
|
/// },
|
||||||
|
/// section! {
|
||||||
|
/// "Test" => TestAction,
|
||||||
|
/// "Test2" => TestAction,
|
||||||
|
/// "Test Value" => TestU8Action(1_u8),
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// };
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Macro expansion
|
||||||
|
///
|
||||||
|
/// The code generation for the example above looks like this (plus comments):
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn gettext(string: &str) -> String {
|
||||||
|
/// # string.to_owned()
|
||||||
|
/// # }
|
||||||
|
/// #
|
||||||
|
/// struct WindowActionGroup;
|
||||||
|
/// impl relm4::actions::ActionGroupName for WindowActionGroup {
|
||||||
|
/// const NAME: &'static str = "win";
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// struct TestAction;
|
||||||
|
/// impl relm4::actions::ActionName for TestAction {
|
||||||
|
/// type Group = WindowActionGroup;
|
||||||
|
/// type State = ();
|
||||||
|
/// type Target = ();
|
||||||
|
///
|
||||||
|
/// const NAME: &'static str = "test";
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// struct TestU8Action;
|
||||||
|
/// impl relm4::actions::ActionName for TestU8Action {
|
||||||
|
/// type Group = WindowActionGroup;
|
||||||
|
/// type State = u8;
|
||||||
|
/// type Target = u8;
|
||||||
|
///
|
||||||
|
/// const NAME: &'static str = "test2";
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // Main menu
|
||||||
|
/// let main_menu = relm4::gtk::gio::Menu::new();
|
||||||
|
///
|
||||||
|
/// // Placeholder for custom widget
|
||||||
|
/// let new_entry = relm4::gtk::gio::MenuItem::new(None, None);
|
||||||
|
/// let variant = relm4::gtk::glib::variant::ToVariant::to_variant("my_widget");
|
||||||
|
/// new_entry.set_attribute_value("custom", Some(&variant));
|
||||||
|
/// main_menu.append_item(&new_entry);
|
||||||
|
///
|
||||||
|
/// let new_entry = relm4::actions::RelmAction::<TestAction>::to_menu_item(&gettext("Test"));
|
||||||
|
/// main_menu.append_item(&new_entry);
|
||||||
|
/// let new_entry = relm4::actions::RelmAction::<TestAction>::to_menu_item("Test2");
|
||||||
|
/// main_menu.append_item(&new_entry);
|
||||||
|
/// let new_entry = relm4::actions::RelmAction::<TestU8Action>::to_menu_item_with_target_value(
|
||||||
|
/// "Test toggle",
|
||||||
|
/// &1_u8,
|
||||||
|
/// );
|
||||||
|
/// main_menu.append_item(&new_entry);
|
||||||
|
///
|
||||||
|
/// // Section 0
|
||||||
|
/// let _section_0 = relm4::gtk::gio::Menu::new();
|
||||||
|
/// main_menu.append_section(None, &_section_0);
|
||||||
|
/// let new_entry = relm4::actions::RelmAction::<TestAction>::to_menu_item("Section test");
|
||||||
|
/// _section_0.append_item(&new_entry);
|
||||||
|
/// let new_entry = relm4::actions::RelmAction::<TestU8Action>::to_menu_item_with_target_value(
|
||||||
|
/// "Test toggle",
|
||||||
|
/// &1_u8,
|
||||||
|
/// );
|
||||||
|
/// _section_0.append_item(&new_entry);
|
||||||
|
///
|
||||||
|
/// // Section 1
|
||||||
|
/// let _section_1 = relm4::gtk::gio::Menu::new();
|
||||||
|
/// main_menu.append_section(None, &_section_1);
|
||||||
|
/// let new_entry = relm4::actions::RelmAction::<TestAction>::to_menu_item("Test");
|
||||||
|
/// _section_1.append_item(&new_entry);
|
||||||
|
/// let new_entry = relm4::actions::RelmAction::<TestAction>::to_menu_item("Test2");
|
||||||
|
/// _section_1.append_item(&new_entry);
|
||||||
|
/// let new_entry = relm4::actions::RelmAction::<TestU8Action>::to_menu_item_with_target_value(
|
||||||
|
/// "Test Value",
|
||||||
|
/// &1_u8,
|
||||||
|
/// );
|
||||||
|
/// _section_1.append_item(&new_entry);
|
||||||
|
/// ```
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn menu(input: TokenStream) -> TokenStream {
|
||||||
|
let menus = parse_macro_input!(input as Menus);
|
||||||
|
menus.menus_stream().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The [`view!`] macro allows you to construct your UI easily and cleanly.
|
||||||
|
///
|
||||||
|
/// It does the same as inside the [`macro@component`] attribute macro,
|
||||||
|
/// but with less features.
|
||||||
|
///
|
||||||
|
/// You can even use the `relm4-macros` crate independently from Relm4 to build your GTK4 UI.
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use gtk::prelude::{BoxExt, ButtonExt};
|
||||||
|
/// use relm4::gtk;
|
||||||
|
///
|
||||||
|
/// // Creating a box with a button inside.
|
||||||
|
/// relm4_macros::view! {
|
||||||
|
/// vbox = gtk::Box {
|
||||||
|
/// gtk::Button {
|
||||||
|
/// set_label: "Click me!",
|
||||||
|
/// connect_clicked => |_| {
|
||||||
|
/// println!("Hello world!");
|
||||||
|
/// }
|
||||||
|
/// },
|
||||||
|
/// prepend: my_label = >k::Label::builder()
|
||||||
|
/// .label("The view macro works!")
|
||||||
|
/// .build(),
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // You can simply use the vbox created in the macro.
|
||||||
|
/// let spacing = vbox.spacing();
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Also, the macro doesn't rely on any special gtk4-rs features
|
||||||
|
/// so you can even use the macro for other purposes.
|
||||||
|
///
|
||||||
|
/// In this example, we use it to construct a [`Command`](std::process::Command).
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use std::process::Command;
|
||||||
|
///
|
||||||
|
/// let path = "/";
|
||||||
|
///
|
||||||
|
/// relm4_macros::view! {
|
||||||
|
/// mut process = Command::new("ls") {
|
||||||
|
/// args: ["-la"],
|
||||||
|
/// current_dir = mut &String {
|
||||||
|
/// push_str: path,
|
||||||
|
/// },
|
||||||
|
/// env: ("HOME", "/home/relm4"),
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // Output of "ls -la" at "/"
|
||||||
|
/// dbg!(process.output());
|
||||||
|
/// ```
|
||||||
|
/// # Macro expansion
|
||||||
|
///
|
||||||
|
/// Let's have a look the this example:
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// # use gtk::prelude::{BoxExt, ButtonExt};
|
||||||
|
/// # use relm4::gtk;
|
||||||
|
/// // Creating a box with a button inside.
|
||||||
|
/// relm4_macros::view! {
|
||||||
|
/// vbox = gtk::Box {
|
||||||
|
/// gtk::Button {
|
||||||
|
/// set_label: "Click me!",
|
||||||
|
/// connect_clicked => |_| {
|
||||||
|
/// println!("Hello world!");
|
||||||
|
/// }
|
||||||
|
/// },
|
||||||
|
/// prepend: my_label = >k::Label::builder()
|
||||||
|
/// .label("The view macro works!")
|
||||||
|
/// .build(),
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The code generation for this example looks like this (plus comments):
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// # use gtk::prelude::{BoxExt, ButtonExt};
|
||||||
|
/// # use relm4::gtk;
|
||||||
|
///
|
||||||
|
/// // We've just used `gtk::Box` so we assume it has a `default()` method
|
||||||
|
/// let vbox = gtk::Box::default();
|
||||||
|
/// // `vbox` was named, yet the button doesn't have an explicit name and gets a generated one instead.
|
||||||
|
/// let _gtk_button_5 = gtk::Button::default();
|
||||||
|
/// // For the label, we used a manual constructor method, so no `default()` method is required.
|
||||||
|
/// let my_label = gtk::Label::builder().label("The view macro works!").build();
|
||||||
|
///
|
||||||
|
/// // Connect the signal
|
||||||
|
/// {
|
||||||
|
/// _gtk_button_5.connect_clicked(|_| {
|
||||||
|
/// println!("Hello world!");
|
||||||
|
/// });
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // The button was added without any further instructions, so we assume `container_add()` will work.
|
||||||
|
/// relm4::RelmContainerExt::container_add(&vbox, &_gtk_button_5);
|
||||||
|
/// _gtk_button_5.set_label("Click me!");
|
||||||
|
/// // For the label, we used the `prepend` method, so we don't need `container_add()` here.
|
||||||
|
/// vbox.prepend(&my_label);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The widgets are first initialized, then signals are connected and then
|
||||||
|
/// properties and widgets are assigned to each other.
|
||||||
|
///
|
||||||
|
/// The nested structure of the UI is translated into regular Rust code.
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn view(input: TokenStream) -> TokenStream {
|
||||||
|
view::generate_tokens(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A macro to generate widget templates.
|
||||||
|
///
|
||||||
|
/// This macro generates a new type that implements `relm4::WidgetTemplate`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use relm4::prelude::*;
|
||||||
|
/// use gtk::prelude::*;
|
||||||
|
///
|
||||||
|
/// #[relm4::widget_template]
|
||||||
|
/// impl WidgetTemplate for MyBox {
|
||||||
|
/// view! {
|
||||||
|
/// gtk::Box {
|
||||||
|
/// set_margin_all: 10,
|
||||||
|
/// // Make the boxes visible
|
||||||
|
/// inline_css: "border: 2px solid blue",
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The template allows you the generate deeply nested
|
||||||
|
/// structures. All named items will be directly accessible
|
||||||
|
/// as a child of the template, even if they are nested.
|
||||||
|
/// In this example the "child_label" is a template child.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use relm4::prelude::*;
|
||||||
|
/// # use gtk::prelude::*;
|
||||||
|
/// #
|
||||||
|
/// # #[relm4::widget_template]
|
||||||
|
/// # impl WidgetTemplate for MyBox {
|
||||||
|
/// # view! {
|
||||||
|
/// # gtk::Box {
|
||||||
|
/// # set_margin_all: 10,
|
||||||
|
/// # // Make the boxes visible
|
||||||
|
/// # inline_css: "border: 2px solid blue",
|
||||||
|
/// # }
|
||||||
|
/// # }
|
||||||
|
/// # }
|
||||||
|
/// #
|
||||||
|
/// #[relm4::widget_template]
|
||||||
|
/// impl WidgetTemplate for MySpinner {
|
||||||
|
/// view! {
|
||||||
|
/// gtk::Spinner {
|
||||||
|
/// set_spinning: true,
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[relm4::widget_template]
|
||||||
|
/// impl WidgetTemplate for CustomBox {
|
||||||
|
/// view! {
|
||||||
|
/// gtk::Box {
|
||||||
|
/// set_orientation: gtk::Orientation::Vertical,
|
||||||
|
/// set_margin_all: 5,
|
||||||
|
/// set_spacing: 5,
|
||||||
|
///
|
||||||
|
/// #[template]
|
||||||
|
/// MyBox {
|
||||||
|
/// #[template]
|
||||||
|
/// MySpinner,
|
||||||
|
///
|
||||||
|
/// #[template]
|
||||||
|
/// MyBox {
|
||||||
|
/// #[template]
|
||||||
|
/// MySpinner,
|
||||||
|
///
|
||||||
|
/// #[template]
|
||||||
|
/// MyBox {
|
||||||
|
/// #[template]
|
||||||
|
/// MySpinner,
|
||||||
|
///
|
||||||
|
/// // Deeply nested!
|
||||||
|
/// #[name = "child_label"]
|
||||||
|
/// gtk::Label {
|
||||||
|
/// set_label: "This is a test",
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn widget_template(attributes: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
let SyncOnlyAttrs { visibility } = parse_macro_input!(attributes);
|
||||||
|
|
||||||
|
let item_impl = parse_macro_input!(input as ItemImpl);
|
||||||
|
widget_template::generate_tokens(visibility, item_impl).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[rustversion::all(stable, since(1.69))]
|
||||||
|
mod test {
|
||||||
|
#[test]
|
||||||
|
fn ui() {
|
||||||
|
let t = trybuild::TestCases::new();
|
||||||
|
t.compile_fail("tests/ui/compile-fail/**/*.rs");
|
||||||
|
}
|
||||||
|
}
|
||||||
136
Relm4-0.6.2/relm4-macros/src/menu/gen.rs
Normal file
136
Relm4-0.6.2/relm4-macros/src/menu/gen.rs
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
use proc_macro2::{Span as Span2, TokenStream as TokenStream2};
|
||||||
|
use quote::{quote, quote_spanned};
|
||||||
|
use syn::{spanned::Spanned, Ident, LitStr};
|
||||||
|
|
||||||
|
use super::{Menu, MenuElement, MenuEntry, MenuItem, MenuSection, Menus, SubMenu};
|
||||||
|
|
||||||
|
impl Menus {
|
||||||
|
pub(crate) fn menus_stream(&self) -> TokenStream2 {
|
||||||
|
let mut menu_stream = TokenStream2::new();
|
||||||
|
|
||||||
|
for item in &self.items {
|
||||||
|
menu_stream.extend(item.menu_stream());
|
||||||
|
}
|
||||||
|
|
||||||
|
menu_stream
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Menu {
|
||||||
|
fn menu_stream(&self) -> TokenStream2 {
|
||||||
|
let name = &self.name;
|
||||||
|
let gtk_import = crate::gtk_import();
|
||||||
|
|
||||||
|
// Create new menu
|
||||||
|
let mut menu_stream = quote_spanned! {
|
||||||
|
name.span() =>
|
||||||
|
let #name = #gtk_import ::gio::Menu::new();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add items
|
||||||
|
for item in &self.items {
|
||||||
|
menu_stream.extend(item.item_stream(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
menu_stream
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MenuElement {
|
||||||
|
fn item_stream(&self, parent_ident: &Ident) -> TokenStream2 {
|
||||||
|
let mut item_stream = TokenStream2::new();
|
||||||
|
|
||||||
|
item_stream.extend(match self {
|
||||||
|
Self::Item(entry) => entry.item_stream(parent_ident),
|
||||||
|
Self::Section(section) => section.section_stream(parent_ident),
|
||||||
|
Self::Custom(id) => custom_stream(parent_ident, id),
|
||||||
|
});
|
||||||
|
|
||||||
|
item_stream
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn custom_stream(parent_ident: &Ident, id: &LitStr) -> TokenStream2 {
|
||||||
|
let gtk_import = crate::gtk_import();
|
||||||
|
quote_spanned! {
|
||||||
|
id.span() =>
|
||||||
|
let new_entry = #gtk_import::gio::MenuItem::new(None, None);
|
||||||
|
let variant = #gtk_import::glib::variant::ToVariant::to_variant(#id);
|
||||||
|
new_entry.set_attribute_value("custom", Some(&variant));
|
||||||
|
#parent_ident.append_item(&new_entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MenuItem {
|
||||||
|
fn item_stream(&self, parent_ident: &Ident) -> TokenStream2 {
|
||||||
|
match self {
|
||||||
|
Self::Entry(entry) => entry.entry_stream(parent_ident),
|
||||||
|
Self::SubMenu(sub_menu) => sub_menu.submenu_stream(parent_ident),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SubMenu {
|
||||||
|
fn submenu_stream(&self, parent_ident: &Ident) -> TokenStream2 {
|
||||||
|
let name = Ident::new(&format!("_{parent_ident}"), Span2::call_site());
|
||||||
|
let gtk_import = crate::gtk_import();
|
||||||
|
let expr = &self.expr;
|
||||||
|
|
||||||
|
// Create new sub-menu
|
||||||
|
let mut item_stream = quote_spanned! {
|
||||||
|
expr.span() =>
|
||||||
|
let #name = #gtk_import ::gio::Menu::new();
|
||||||
|
#parent_ident.append_submenu(Some(#expr), &#name);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add items
|
||||||
|
for item in &self.items {
|
||||||
|
item_stream.extend(item.item_stream(&name));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap the generated code in a new scope to avoid side-effects
|
||||||
|
quote! {
|
||||||
|
{
|
||||||
|
#item_stream
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MenuEntry {
|
||||||
|
fn entry_stream(&self, parent_ident: &Ident) -> TokenStream2 {
|
||||||
|
let expr = &self.expr;
|
||||||
|
let ty = &self.action_ty;
|
||||||
|
|
||||||
|
if let Some(value) = &self.value {
|
||||||
|
quote_spanned! {
|
||||||
|
expr.span() =>
|
||||||
|
let new_entry = relm4::actions::RelmAction::<#ty>::to_menu_item_with_target_value(#expr, &#value);
|
||||||
|
#parent_ident.append_item(&new_entry);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote_spanned! {
|
||||||
|
expr.span() =>
|
||||||
|
let new_entry = relm4::actions::RelmAction::<#ty>::to_menu_item(#expr);
|
||||||
|
#parent_ident.append_item(&new_entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MenuSection {
|
||||||
|
fn section_stream(&self, parent_ident: &Ident) -> TokenStream2 {
|
||||||
|
let name = &self.name;
|
||||||
|
let gtk_import = crate::gtk_import();
|
||||||
|
let mut section_stream = quote! {
|
||||||
|
let #name = #gtk_import::gio::Menu::new();
|
||||||
|
#parent_ident.append_section(None, &#name);
|
||||||
|
};
|
||||||
|
|
||||||
|
for item in &self.items {
|
||||||
|
section_stream.extend(item.item_stream(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
section_stream
|
||||||
|
}
|
||||||
|
}
|
||||||
49
Relm4-0.6.2/relm4-macros/src/menu/mod.rs
Normal file
49
Relm4-0.6.2/relm4-macros/src/menu/mod.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
use syn::punctuated::Punctuated;
|
||||||
|
use syn::token::Comma;
|
||||||
|
use syn::{Expr, Ident, LitStr, Path};
|
||||||
|
|
||||||
|
mod gen;
|
||||||
|
mod parse;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct Menus {
|
||||||
|
items: Punctuated<Menu, Comma>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Menu {
|
||||||
|
name: Ident,
|
||||||
|
items: Punctuated<MenuElement, Comma>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum MenuElement {
|
||||||
|
Item(Box<MenuItem>),
|
||||||
|
Custom(LitStr),
|
||||||
|
Section(MenuSection),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum MenuItem {
|
||||||
|
Entry(Box<MenuEntry>),
|
||||||
|
SubMenu(Box<SubMenu>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct MenuEntry {
|
||||||
|
expr: Expr,
|
||||||
|
action_ty: Path,
|
||||||
|
value: Option<Expr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct SubMenu {
|
||||||
|
expr: Expr,
|
||||||
|
items: Punctuated<MenuElement, Comma>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct MenuSection {
|
||||||
|
name: Ident,
|
||||||
|
items: Punctuated<MenuElement, Comma>,
|
||||||
|
}
|
||||||
103
Relm4-0.6.2/relm4-macros/src/menu/parse.rs
Normal file
103
Relm4-0.6.2/relm4-macros/src/menu/parse.rs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
use proc_macro2::Span as Span2;
|
||||||
|
use syn::parse::{Parse, ParseStream};
|
||||||
|
use syn::punctuated::Punctuated;
|
||||||
|
use syn::{braced, parenthesized, token, Ident, Path, Result, Token};
|
||||||
|
|
||||||
|
use crate::menu::SubMenu;
|
||||||
|
|
||||||
|
use super::{Menu, MenuElement, MenuEntry, MenuItem, MenuSection, Menus};
|
||||||
|
|
||||||
|
syn::custom_keyword!(custom);
|
||||||
|
|
||||||
|
impl Parse for Menus {
|
||||||
|
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||||
|
let items = input.call(Punctuated::parse_separated_nonempty)?;
|
||||||
|
|
||||||
|
Ok(Menus { items })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Menu {
|
||||||
|
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||||
|
let name = input.parse()?;
|
||||||
|
let _colon: Token![:] = input.parse()?;
|
||||||
|
|
||||||
|
let braced_input;
|
||||||
|
braced!(braced_input in input);
|
||||||
|
|
||||||
|
let items = braced_input.call(Punctuated::parse_terminated)?;
|
||||||
|
|
||||||
|
Ok(Menu { name, items })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for MenuItem {
|
||||||
|
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||||
|
let expr = input.parse()?;
|
||||||
|
|
||||||
|
Ok(if input.peek(Token![=>]) {
|
||||||
|
let _arrow: Token![=>] = input.parse()?;
|
||||||
|
let action_ty = input.call(Path::parse_mod_style)?;
|
||||||
|
|
||||||
|
let value = if input.peek(token::Paren) {
|
||||||
|
let paren_input;
|
||||||
|
parenthesized!(paren_input in input);
|
||||||
|
Some(paren_input.parse()?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Self::Entry(Box::new(MenuEntry {
|
||||||
|
expr,
|
||||||
|
action_ty,
|
||||||
|
value,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
let braced_input;
|
||||||
|
braced!(braced_input in input);
|
||||||
|
|
||||||
|
let items = braced_input.call(Punctuated::parse_terminated)?;
|
||||||
|
|
||||||
|
Self::SubMenu(Box::new(SubMenu { expr, items }))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for MenuElement {
|
||||||
|
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||||
|
Ok(if input.peek(custom) {
|
||||||
|
let _custom: custom = input.parse()?;
|
||||||
|
let _colon: Token![:] = input.parse()?;
|
||||||
|
input.parse().map(MenuElement::Custom)?
|
||||||
|
} else if input.peek2(Token![!]) {
|
||||||
|
input.parse().map(MenuElement::Section)?
|
||||||
|
} else {
|
||||||
|
input.parse().map(MenuElement::Item)?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for MenuSection {
|
||||||
|
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||||
|
let name: Ident = input.parse()?;
|
||||||
|
assert!(name == "section");
|
||||||
|
let _excl: Token![!] = input.parse()?;
|
||||||
|
|
||||||
|
let braced_input;
|
||||||
|
braced!(braced_input in input);
|
||||||
|
|
||||||
|
let items = braced_input.call(Punctuated::parse_terminated)?;
|
||||||
|
let name = section_name();
|
||||||
|
|
||||||
|
Ok(MenuSection { name, items })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn section_name() -> Ident {
|
||||||
|
use std::sync::atomic::{AtomicU8, Ordering};
|
||||||
|
static COUNTER: AtomicU8 = AtomicU8::new(0);
|
||||||
|
|
||||||
|
let value = COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||||
|
|
||||||
|
Ident::new(&format!("_section_{value}"), Span2::call_site())
|
||||||
|
}
|
||||||
147
Relm4-0.6.2/relm4-macros/src/token_streams.rs
Normal file
147
Relm4-0.6.2/relm4-macros/src/token_streams.rs
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::{quote, ToTokens};
|
||||||
|
use syn::{Error, Ident, Visibility};
|
||||||
|
|
||||||
|
use crate::widgets::{TopLevelWidget, ViewWidgets, Widget};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub(super) struct TokenStreams {
|
||||||
|
/// Parsing errors
|
||||||
|
pub(super) error: TokenStream2,
|
||||||
|
/// Initialize the root widget.
|
||||||
|
pub(super) init_root: TokenStream2,
|
||||||
|
/// Rename root to the actual widget name.
|
||||||
|
pub(super) rename_root: TokenStream2,
|
||||||
|
/// The tokens for the struct fields -> name: Type,
|
||||||
|
pub(super) struct_fields: TokenStream2,
|
||||||
|
/// The tokens initializing the widgets.
|
||||||
|
pub(super) init: TokenStream2,
|
||||||
|
/// The tokens initializing the properties.
|
||||||
|
pub(super) assign: TokenStream2,
|
||||||
|
/// The tokens for the returned struct fields -> name,
|
||||||
|
pub(super) return_fields: TokenStream2,
|
||||||
|
/// For destructuring the widget struct field
|
||||||
|
pub(super) destructure_fields: TokenStream2,
|
||||||
|
/// The view tokens (watch! macro)
|
||||||
|
pub(super) update_view: TokenStream2,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct TraitImplDetails {
|
||||||
|
/// The visibility of the widgets struct.
|
||||||
|
pub(super) vis: Option<Visibility>,
|
||||||
|
/// The name of the model.
|
||||||
|
pub(super) model_name: Ident,
|
||||||
|
/// The name of the root widget.
|
||||||
|
pub(super) root_name: Option<Ident>,
|
||||||
|
/// The name of the sender used in the init function.
|
||||||
|
pub(super) sender_name: Ident,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ViewWidgets {
|
||||||
|
pub(super) fn generate_streams(
|
||||||
|
&self,
|
||||||
|
trait_impl_details: &TraitImplDetails,
|
||||||
|
standalone_view: bool,
|
||||||
|
) -> TokenStreams {
|
||||||
|
let mut streams = TokenStreams::default();
|
||||||
|
|
||||||
|
for top_level_widget in &self.top_level_widgets {
|
||||||
|
top_level_widget.generate_streams(&mut streams, trait_impl_details, standalone_view);
|
||||||
|
}
|
||||||
|
|
||||||
|
streams
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the root widget
|
||||||
|
pub(super) fn get_root_widget(&self) -> syn::Result<&Widget> {
|
||||||
|
self.top_level_widgets
|
||||||
|
.iter()
|
||||||
|
.find(|w| w.root_attr.is_some())
|
||||||
|
.map(|w| &w.inner)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
Error::new(
|
||||||
|
self.span,
|
||||||
|
"You need to specify the root widget using the `#[root]` attribute.",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate root type for `Root` parameter in `Component` impl
|
||||||
|
pub(super) fn root_type(&self) -> TokenStream2 {
|
||||||
|
match self.get_root_widget() {
|
||||||
|
Ok(root_widget) => root_widget.func_type_token_stream(),
|
||||||
|
Err(err) => err.to_compile_error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the name of the root widget
|
||||||
|
pub(super) fn root_name(&self) -> TokenStream2 {
|
||||||
|
match self.get_root_widget() {
|
||||||
|
Ok(root_widget) => root_widget.name.to_token_stream(),
|
||||||
|
Err(err) => err.to_compile_error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TopLevelWidget {
|
||||||
|
fn generate_streams(
|
||||||
|
&self,
|
||||||
|
streams: &mut TokenStreams,
|
||||||
|
trait_impl_details: &TraitImplDetails,
|
||||||
|
standalone_view: bool,
|
||||||
|
) {
|
||||||
|
let generate_init_root_stream = !standalone_view && self.root_attr.is_some();
|
||||||
|
self.inner
|
||||||
|
.init_token_generation(streams, trait_impl_details, generate_init_root_stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget {
|
||||||
|
pub(super) fn init_token_generation(
|
||||||
|
&self,
|
||||||
|
streams: &mut TokenStreams,
|
||||||
|
trait_impl_details: &TraitImplDetails,
|
||||||
|
generate_root_init_stream: bool,
|
||||||
|
) {
|
||||||
|
let TraitImplDetails {
|
||||||
|
vis,
|
||||||
|
model_name,
|
||||||
|
root_name,
|
||||||
|
sender_name,
|
||||||
|
} = trait_impl_details;
|
||||||
|
|
||||||
|
let name = &self.name;
|
||||||
|
|
||||||
|
// Initialize the root
|
||||||
|
if generate_root_init_stream {
|
||||||
|
// For the `component` macro
|
||||||
|
self.init_root_init_streams(&mut streams.init_root, &mut streams.init);
|
||||||
|
} else {
|
||||||
|
// For the `view!` macro
|
||||||
|
self.init_stream(&mut streams.init);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "relm4")]
|
||||||
|
streams.assign.extend(quote::quote! {
|
||||||
|
use relm4::RelmContainerExt as _;
|
||||||
|
});
|
||||||
|
|
||||||
|
self.error_stream(&mut streams.error);
|
||||||
|
self.start_assign_stream(&mut streams.assign, sender_name);
|
||||||
|
self.init_conditional_init_stream(&mut streams.assign, model_name);
|
||||||
|
self.struct_fields_stream(&mut streams.struct_fields, vis);
|
||||||
|
self.return_stream(&mut streams.return_fields);
|
||||||
|
self.destructure_stream(&mut streams.destructure_fields);
|
||||||
|
self.init_update_view_stream(&mut streams.update_view, model_name);
|
||||||
|
|
||||||
|
// Rename the `root` to the actual widget name
|
||||||
|
if generate_root_init_stream {
|
||||||
|
let mut_token = self.mutable.as_ref();
|
||||||
|
if let Some(root_name) = root_name {
|
||||||
|
streams.rename_root.extend(quote! {
|
||||||
|
let #mut_token #name = #root_name.clone();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
135
Relm4-0.6.2/relm4-macros/src/util.rs
Normal file
135
Relm4-0.6.2/relm4-macros/src/util.rs
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
use proc_macro::TokenStream;
|
||||||
|
use proc_macro2::{Span as Span2, TokenStream as TokenStream2};
|
||||||
|
use syn::punctuated::Punctuated;
|
||||||
|
|
||||||
|
use syn::spanned::Spanned;
|
||||||
|
use syn::{FnArg, Ident, ImplItem, ItemImpl, Path, PathArguments, PathSegment, Type, TypePath};
|
||||||
|
|
||||||
|
pub(super) fn generate_widgets_type(
|
||||||
|
widgets_ty: Option<Type>,
|
||||||
|
component_impl: &mut ItemImpl,
|
||||||
|
errors: &mut Vec<syn::Error>,
|
||||||
|
) -> Option<Type> {
|
||||||
|
// Use the widget type if used.
|
||||||
|
if let Some(ty) = widgets_ty {
|
||||||
|
Some(ty)
|
||||||
|
}
|
||||||
|
// Try to generate the type from the self type name.
|
||||||
|
else if let Type::Path(self_ty) = &*component_impl.self_ty {
|
||||||
|
let (path, impl_item) = self_ty_to_widgets_ty(self_ty);
|
||||||
|
component_impl.items.push(impl_item);
|
||||||
|
Some(path)
|
||||||
|
}
|
||||||
|
// Error: No Widget type found or generated.
|
||||||
|
else {
|
||||||
|
let msg = "no `Widgets` type found and the type if `Self` in not a path. \
|
||||||
|
Please use a path for `Self` or use `type Widgets = WidgetsName;` to name the widgets type.";
|
||||||
|
errors.push(syn::Error::new(
|
||||||
|
component_impl
|
||||||
|
.items
|
||||||
|
.first()
|
||||||
|
.map(|i| i.span())
|
||||||
|
.unwrap_or_else(|| component_impl.self_ty.span()),
|
||||||
|
msg,
|
||||||
|
));
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn self_ty_to_widgets_ty(self_ty: &TypePath) -> (Type, ImplItem) {
|
||||||
|
// Retrieve path, remove any generics and append "Widgets" to the last segment.
|
||||||
|
let mut self_path = self_ty.clone();
|
||||||
|
let last_seg = self_path.path.segments.last_mut().unwrap();
|
||||||
|
last_seg.arguments = Default::default();
|
||||||
|
last_seg.ident = Ident::new(&format!("{}Widgets", last_seg.ident), last_seg.span());
|
||||||
|
|
||||||
|
// Generate impl item for the trait impl
|
||||||
|
let impl_item = syn::parse_quote_spanned! {
|
||||||
|
self_path.span() => type Widgets = #self_path;
|
||||||
|
};
|
||||||
|
|
||||||
|
(Type::Path(self_path), impl_item)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn strings_to_path(strings: &[&str]) -> Path {
|
||||||
|
let path_segments: Vec<PathSegment> = strings
|
||||||
|
.iter()
|
||||||
|
.map(|string| -> PathSegment {
|
||||||
|
PathSegment {
|
||||||
|
ident: Ident::new(string, Span2::call_site()),
|
||||||
|
arguments: PathArguments::None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Path {
|
||||||
|
leading_colon: None,
|
||||||
|
segments: Punctuated::from_iter(path_segments),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn item_impl_error(original_input: TokenStream) -> TokenStream {
|
||||||
|
let macro_impls = quote::quote! {
|
||||||
|
macro_rules! view_output {
|
||||||
|
() => { () };
|
||||||
|
}
|
||||||
|
macro_rules! view {
|
||||||
|
() => {};
|
||||||
|
($tt:tt) => {};
|
||||||
|
($tt:tt $($y:tt)+) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.into();
|
||||||
|
vec![macro_impls, original_input].into_iter().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn verbatim_impl_item_fn(
|
||||||
|
name: &str,
|
||||||
|
args: Vec<FnArg>,
|
||||||
|
ty: Type,
|
||||||
|
tokens: TokenStream2,
|
||||||
|
) -> ImplItem {
|
||||||
|
ImplItem::Fn(syn::ImplItemFn {
|
||||||
|
attrs: Vec::new(),
|
||||||
|
vis: syn::Visibility::Inherited,
|
||||||
|
defaultness: None,
|
||||||
|
sig: syn::Signature {
|
||||||
|
constness: None,
|
||||||
|
asyncness: None,
|
||||||
|
unsafety: None,
|
||||||
|
abi: None,
|
||||||
|
fn_token: syn::token::Fn::default(),
|
||||||
|
ident: Ident::new(name, Span2::call_site()),
|
||||||
|
generics: syn::Generics {
|
||||||
|
lt_token: None,
|
||||||
|
params: Punctuated::default(),
|
||||||
|
gt_token: None,
|
||||||
|
where_clause: None,
|
||||||
|
},
|
||||||
|
paren_token: syn::token::Paren::default(),
|
||||||
|
inputs: Punctuated::from_iter(args),
|
||||||
|
variadic: None,
|
||||||
|
output: syn::ReturnType::Type(syn::token::RArrow::default(), Box::new(ty)),
|
||||||
|
},
|
||||||
|
block: syn::Block {
|
||||||
|
brace_token: syn::token::Brace::default(),
|
||||||
|
stmts: vec![syn::Stmt::Expr(syn::Expr::Verbatim(tokens), None)],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the identifier for a parameter if it is a binding with an identifier pattern.
|
||||||
|
pub(super) fn extract_arg_ident(arg: &FnArg) -> syn::Result<&Ident> {
|
||||||
|
match arg {
|
||||||
|
syn::FnArg::Typed(pat_type) => match &*pat_type.pat {
|
||||||
|
syn::Pat::Ident(ident) => Ok(&ident.ident),
|
||||||
|
pat => Err(syn::Error::new_spanned(
|
||||||
|
pat,
|
||||||
|
"parameter binding must be an identifier",
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
syn::FnArg::Receiver(_) => Err(syn::Error::new_spanned(
|
||||||
|
arg,
|
||||||
|
"parameter binding must be an identifier",
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
35
Relm4-0.6.2/relm4-macros/src/view.rs
Normal file
35
Relm4-0.6.2/relm4-macros/src/view.rs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
use proc_macro::TokenStream;
|
||||||
|
use proc_macro2::Span as Span2;
|
||||||
|
use quote::quote;
|
||||||
|
use syn::{parse_macro_input, Ident};
|
||||||
|
|
||||||
|
use crate::token_streams::{TokenStreams, TraitImplDetails};
|
||||||
|
use crate::widgets::ViewWidgets;
|
||||||
|
|
||||||
|
pub(super) fn generate_tokens(input: TokenStream) -> TokenStream {
|
||||||
|
let view_widgets: ViewWidgets = parse_macro_input!(input);
|
||||||
|
|
||||||
|
let TokenStreams {
|
||||||
|
error,
|
||||||
|
init,
|
||||||
|
assign,
|
||||||
|
..
|
||||||
|
} = view_widgets.generate_streams(
|
||||||
|
&TraitImplDetails {
|
||||||
|
vis: None,
|
||||||
|
model_name: Ident::new("_", Span2::call_site()),
|
||||||
|
sender_name: Ident::new("sender", Span2::call_site()),
|
||||||
|
root_name: None,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
let output = quote! {
|
||||||
|
#init
|
||||||
|
#assign
|
||||||
|
{
|
||||||
|
#error
|
||||||
|
}
|
||||||
|
};
|
||||||
|
output.into()
|
||||||
|
}
|
||||||
479
Relm4-0.6.2/relm4-macros/src/visitors.rs
Normal file
479
Relm4-0.6.2/relm4-macros/src/visitors.rs
Normal file
@ -0,0 +1,479 @@
|
|||||||
|
use std::mem;
|
||||||
|
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
|
use syn::visit::{self, Visit};
|
||||||
|
use syn::visit_mut::{self, VisitMut};
|
||||||
|
use syn::LocalInit;
|
||||||
|
|
||||||
|
use crate::additional_fields::AdditionalFields;
|
||||||
|
use crate::menu::Menus;
|
||||||
|
use crate::util;
|
||||||
|
use crate::widgets::ViewWidgets;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(super) struct ComponentVisitor<'errors> {
|
||||||
|
pub(super) view_widgets: Option<syn::Result<ViewWidgets>>,
|
||||||
|
pub(super) widgets_ty: Option<syn::Type>,
|
||||||
|
pub(super) root_name: Option<syn::Ident>,
|
||||||
|
pub(super) model_name: Option<syn::Ident>,
|
||||||
|
pub(super) sender_name: Option<syn::Ident>,
|
||||||
|
pub(super) additional_fields: Option<AdditionalFields>,
|
||||||
|
pub(super) menus: Option<Menus>,
|
||||||
|
pub(super) errors: &'errors mut Vec<syn::Error>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'errors> ComponentVisitor<'errors> {
|
||||||
|
pub(super) fn new(errors: &'errors mut Vec<syn::Error>) -> Self {
|
||||||
|
ComponentVisitor {
|
||||||
|
view_widgets: None,
|
||||||
|
widgets_ty: None,
|
||||||
|
root_name: None,
|
||||||
|
model_name: None,
|
||||||
|
sender_name: None,
|
||||||
|
additional_fields: None,
|
||||||
|
menus: None,
|
||||||
|
errors,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VisitMut for ComponentVisitor<'_> {
|
||||||
|
fn visit_impl_item_mut(&mut self, item: &mut syn::ImplItem) {
|
||||||
|
let mut remove = false;
|
||||||
|
|
||||||
|
match item {
|
||||||
|
syn::ImplItem::Macro(mac) => {
|
||||||
|
match mac.mac.path.get_ident().map(ToString::to_string).as_deref() {
|
||||||
|
Some("view") => {
|
||||||
|
if self.view_widgets.is_some() {
|
||||||
|
self.errors
|
||||||
|
.push(syn::Error::new_spanned(&mac, "duplicate view macro"));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.view_widgets.replace(mac.mac.parse_body());
|
||||||
|
|
||||||
|
remove = true;
|
||||||
|
}
|
||||||
|
Some("additional_fields") => {
|
||||||
|
match mac.mac.parse_body::<AdditionalFields>() {
|
||||||
|
Ok(fields) => {
|
||||||
|
let existing = self.additional_fields.replace(fields);
|
||||||
|
|
||||||
|
if existing.is_some() {
|
||||||
|
self.errors.push(syn::Error::new_spanned(
|
||||||
|
mac,
|
||||||
|
"duplicate additional_fields macro",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.errors.push(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
remove = true;
|
||||||
|
}
|
||||||
|
Some("menu") => {
|
||||||
|
match mac.mac.parse_body::<Menus>() {
|
||||||
|
Ok(menu) => {
|
||||||
|
let existing = self.menus.replace(menu);
|
||||||
|
|
||||||
|
if existing.is_some() {
|
||||||
|
self.errors
|
||||||
|
.push(syn::Error::new_spanned(mac, "duplicate menu macro"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.errors.push(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
remove = true;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
syn::ImplItem::Fn(func) => {
|
||||||
|
if &*func.sig.ident.to_string() == "init" {
|
||||||
|
let mut init_fn_visitor = InitFnVisitor::default();
|
||||||
|
init_fn_visitor.visit_impl_item_fn(func);
|
||||||
|
|
||||||
|
self.model_name = init_fn_visitor.model_name;
|
||||||
|
self.sender_name = init_fn_visitor.sender_name;
|
||||||
|
self.root_name = init_fn_visitor.root_name;
|
||||||
|
self.errors.append(&mut init_fn_visitor.errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
if remove {
|
||||||
|
*item = null_item();
|
||||||
|
}
|
||||||
|
|
||||||
|
visit_mut::visit_impl_item_mut(self, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_impl_item_type_mut(&mut self, ty: &mut syn::ImplItemType) {
|
||||||
|
if ty.ident == "Widgets" {
|
||||||
|
self.widgets_ty = Some(ty.ty.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(super) struct FactoryComponentVisitor<'errors> {
|
||||||
|
pub(super) view_widgets: Option<syn::Result<ViewWidgets>>,
|
||||||
|
pub(super) widgets_ty: Option<syn::Type>,
|
||||||
|
pub(super) index_ty: Option<syn::Type>,
|
||||||
|
pub(super) init_widgets: Option<syn::ImplItemFn>,
|
||||||
|
pub(super) root_name: Option<syn::Ident>,
|
||||||
|
pub(super) additional_fields: Option<AdditionalFields>,
|
||||||
|
pub(super) menus: Option<Menus>,
|
||||||
|
pub(super) errors: &'errors mut Vec<syn::Error>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'errors> FactoryComponentVisitor<'errors> {
|
||||||
|
pub(super) fn new(errors: &'errors mut Vec<syn::Error>) -> Self {
|
||||||
|
FactoryComponentVisitor {
|
||||||
|
view_widgets: None,
|
||||||
|
widgets_ty: None,
|
||||||
|
index_ty: None,
|
||||||
|
init_widgets: None,
|
||||||
|
root_name: None,
|
||||||
|
additional_fields: None,
|
||||||
|
menus: None,
|
||||||
|
errors,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VisitMut for FactoryComponentVisitor<'_> {
|
||||||
|
fn visit_impl_item_mut(&mut self, item: &mut syn::ImplItem) {
|
||||||
|
let mut remove = false;
|
||||||
|
|
||||||
|
match item {
|
||||||
|
syn::ImplItem::Macro(mac) => {
|
||||||
|
match mac.mac.path.get_ident().map(ToString::to_string).as_deref() {
|
||||||
|
Some("view") => {
|
||||||
|
if self.view_widgets.is_some() {
|
||||||
|
self.errors
|
||||||
|
.push(syn::Error::new_spanned(&mac, "duplicate view macro"));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.view_widgets.replace(mac.mac.parse_body());
|
||||||
|
|
||||||
|
remove = true;
|
||||||
|
}
|
||||||
|
Some("additional_fields") => {
|
||||||
|
match mac.mac.parse_body::<AdditionalFields>() {
|
||||||
|
Ok(fields) => {
|
||||||
|
let existing = self.additional_fields.replace(fields);
|
||||||
|
|
||||||
|
if existing.is_some() {
|
||||||
|
self.errors.push(syn::Error::new_spanned(
|
||||||
|
mac,
|
||||||
|
"duplicate additional_fields macro",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.errors.push(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
remove = true;
|
||||||
|
}
|
||||||
|
Some("menu") => {
|
||||||
|
match mac.mac.parse_body::<Menus>() {
|
||||||
|
Ok(menu) => {
|
||||||
|
let existing = self.menus.replace(menu);
|
||||||
|
|
||||||
|
if existing.is_some() {
|
||||||
|
self.errors
|
||||||
|
.push(syn::Error::new_spanned(mac, "duplicate menu macro"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.errors.push(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
remove = true;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
syn::ImplItem::Fn(func) => {
|
||||||
|
if &*func.sig.ident.to_string() == "init_widgets" {
|
||||||
|
let mut init_fn_visitor = InitWidgetsFnVisitor::default();
|
||||||
|
init_fn_visitor.visit_impl_item_fn(func);
|
||||||
|
|
||||||
|
self.root_name = init_fn_visitor.root_name;
|
||||||
|
self.errors.append(&mut init_fn_visitor.errors);
|
||||||
|
|
||||||
|
let existing = self.init_widgets.replace(func.clone());
|
||||||
|
if existing.is_some() {
|
||||||
|
self.errors.push(syn::Error::new_spanned(
|
||||||
|
func,
|
||||||
|
"duplicate init_widgets function",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
if remove {
|
||||||
|
*item = null_item();
|
||||||
|
}
|
||||||
|
|
||||||
|
visit_mut::visit_impl_item_mut(self, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_impl_item_type_mut(&mut self, ty: &mut syn::ImplItemType) {
|
||||||
|
if ty.ident == "Widgets" {
|
||||||
|
self.widgets_ty = Some(ty.ty.clone());
|
||||||
|
} else if ty.ident == "Root" {
|
||||||
|
self.errors.push(syn::Error::new_spanned(
|
||||||
|
ty,
|
||||||
|
"`Root` type is defined by `view!` macro",
|
||||||
|
));
|
||||||
|
} else if ty.ident == "Index" {
|
||||||
|
self.index_ty = Some(ty.ty.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct InitFnVisitor {
|
||||||
|
root_name: Option<syn::Ident>,
|
||||||
|
model_name: Option<syn::Ident>,
|
||||||
|
sender_name: Option<syn::Ident>,
|
||||||
|
errors: Vec<syn::Error>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'ast> Visit<'ast> for InitFnVisitor {
|
||||||
|
fn visit_impl_item_fn(&mut self, func: &'ast syn::ImplItemFn) {
|
||||||
|
let Some(root_arg) = func.sig.inputs.iter().nth(1) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let root_name = util::extract_arg_ident(root_arg);
|
||||||
|
|
||||||
|
match root_name {
|
||||||
|
Ok(root_name) => self.root_name = Some(root_name.clone()),
|
||||||
|
Err(e) => self.errors.push(e),
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(sender_arg) = func.sig.inputs.iter().nth(2) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let sender_name = util::extract_arg_ident(sender_arg);
|
||||||
|
|
||||||
|
match sender_name {
|
||||||
|
Ok(sender_name) => self.sender_name = Some(sender_name.clone()),
|
||||||
|
Err(e) => self.errors.push(e),
|
||||||
|
}
|
||||||
|
|
||||||
|
visit::visit_impl_item_fn(self, func);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_expr_struct(&mut self, expr_struct: &'ast syn::ExprStruct) {
|
||||||
|
let ident = &expr_struct.path.segments.last().unwrap().ident;
|
||||||
|
if ident == "ComponentParts" || ident == "AsyncComponentParts" {
|
||||||
|
for field in &expr_struct.fields {
|
||||||
|
let member_name = match &field.member {
|
||||||
|
syn::Member::Named(ident) => Some(ident.to_string()),
|
||||||
|
syn::Member::Unnamed(_) => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if member_name.as_deref() == Some("model") {
|
||||||
|
let model_name = match &field.expr {
|
||||||
|
syn::Expr::Path(path) => {
|
||||||
|
if let Some(ident) = path.path.get_ident() {
|
||||||
|
Ok(ident.clone())
|
||||||
|
} else {
|
||||||
|
Err(syn::Error::new_spanned(
|
||||||
|
path,
|
||||||
|
"unable to determine model name",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Err(syn::Error::new_spanned(
|
||||||
|
&field.expr,
|
||||||
|
"unable to determine model name",
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
|
||||||
|
match model_name {
|
||||||
|
Ok(model_name) => self.model_name = Some(model_name),
|
||||||
|
Err(e) => self.errors.push(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
visit::visit_expr_struct(self, expr_struct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct InitWidgetsFnVisitor {
|
||||||
|
root_name: Option<syn::Ident>,
|
||||||
|
errors: Vec<syn::Error>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'ast> Visit<'ast> for InitWidgetsFnVisitor {
|
||||||
|
fn visit_impl_item_fn(&mut self, func: &'ast syn::ImplItemFn) {
|
||||||
|
let Some(root_arg) = func.sig.inputs.iter().nth(2) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let root_name = util::extract_arg_ident(root_arg);
|
||||||
|
|
||||||
|
match root_name {
|
||||||
|
Ok(root_name) => self.root_name = Some(root_name.clone()),
|
||||||
|
Err(e) => self.errors.push(e),
|
||||||
|
}
|
||||||
|
|
||||||
|
visit::visit_impl_item_fn(self, func);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(super) struct PreAndPostView<'errors> {
|
||||||
|
pub(super) pre_view: Vec<syn::Stmt>,
|
||||||
|
pub(super) post_view: Vec<syn::Stmt>,
|
||||||
|
errors: &'errors mut Vec<syn::Error>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'errors> PreAndPostView<'errors> {
|
||||||
|
pub(super) fn extract(impl_: &mut syn::ItemImpl, errors: &'errors mut Vec<syn::Error>) -> Self {
|
||||||
|
let mut visitor = PreAndPostView {
|
||||||
|
pre_view: vec![],
|
||||||
|
post_view: vec![],
|
||||||
|
errors,
|
||||||
|
};
|
||||||
|
|
||||||
|
visitor.visit_item_impl_mut(impl_);
|
||||||
|
|
||||||
|
visitor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VisitMut for PreAndPostView<'_> {
|
||||||
|
fn visit_impl_item_mut(&mut self, item: &mut syn::ImplItem) {
|
||||||
|
if let syn::ImplItem::Fn(func) = item {
|
||||||
|
match &*func.sig.ident.to_string() {
|
||||||
|
"pre_view" => {
|
||||||
|
if !self.pre_view.is_empty() {
|
||||||
|
self.errors.push(syn::Error::new_spanned(
|
||||||
|
&func,
|
||||||
|
"duplicate pre_view function",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.pre_view = func.block.stmts.clone();
|
||||||
|
*item = null_item();
|
||||||
|
}
|
||||||
|
"post_view" => {
|
||||||
|
if !self.post_view.is_empty() {
|
||||||
|
self.errors.push(syn::Error::new_spanned(
|
||||||
|
&func,
|
||||||
|
"duplicate post_view function",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.post_view = func.block.stmts.clone();
|
||||||
|
*item = null_item();
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
visit_mut::visit_impl_item_mut(self, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expands the `view_output!` macro expression in the `init` function.
|
||||||
|
pub(crate) struct ViewOutputExpander<'errors> {
|
||||||
|
/// Whether a `view_output!` macro expression has been successfully expanded.
|
||||||
|
expanded: bool,
|
||||||
|
|
||||||
|
/// View initialization code to inject before the view output.
|
||||||
|
view_code: TokenStream,
|
||||||
|
|
||||||
|
/// Widgets struct initialization.
|
||||||
|
widgets_init: Box<syn::Expr>,
|
||||||
|
|
||||||
|
errors: &'errors mut Vec<syn::Error>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ViewOutputExpander<'_> {
|
||||||
|
pub(crate) fn expand(
|
||||||
|
item_impl: &mut syn::ItemImpl,
|
||||||
|
view_code: TokenStream,
|
||||||
|
widgets_init: Box<syn::Expr>,
|
||||||
|
errors: &mut Vec<syn::Error>,
|
||||||
|
) {
|
||||||
|
let mut expander = ViewOutputExpander {
|
||||||
|
expanded: false,
|
||||||
|
view_code,
|
||||||
|
widgets_init,
|
||||||
|
errors,
|
||||||
|
};
|
||||||
|
|
||||||
|
expander.visit_item_impl_mut(item_impl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VisitMut for ViewOutputExpander<'_> {
|
||||||
|
fn visit_impl_item_fn_mut(&mut self, method: &mut syn::ImplItemFn) {
|
||||||
|
if method.sig.ident == "init" || method.sig.ident == "init_widgets" {
|
||||||
|
visit_mut::visit_impl_item_fn_mut(self, method);
|
||||||
|
|
||||||
|
if !self.expanded {
|
||||||
|
self.errors.push(syn::Error::new_spanned(method, "expected an injection point for the view macro. Try using `let widgets = view_output!();`"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_stmt_mut(&mut self, stmt: &mut syn::Stmt) {
|
||||||
|
let mut expand = false;
|
||||||
|
|
||||||
|
if let syn::Stmt::Local(syn::Local {
|
||||||
|
init: Some(LocalInit { expr, .. }),
|
||||||
|
..
|
||||||
|
}) = stmt
|
||||||
|
{
|
||||||
|
if let syn::Expr::Macro(mac) = &**expr {
|
||||||
|
if mac.mac.path.is_ident("view_output") {
|
||||||
|
expand = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if expand {
|
||||||
|
// Replace the macro invocation with the widget initialization code. Perform the
|
||||||
|
// swap in-place to avoid a clone.
|
||||||
|
mem::swap(expr, &mut self.widgets_init);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if expand {
|
||||||
|
let view_code = &self.view_code;
|
||||||
|
|
||||||
|
*stmt = syn::Stmt::Expr(
|
||||||
|
syn::Expr::Verbatim(quote! {
|
||||||
|
#view_code
|
||||||
|
#stmt
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.expanded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an empty impl item that can be used to remove an existing item in a mutable visitor.
|
||||||
|
fn null_item() -> syn::ImplItem {
|
||||||
|
syn::ImplItem::Verbatim(quote! {})
|
||||||
|
}
|
||||||
95
Relm4-0.6.2/relm4-macros/src/widget_template.rs
Normal file
95
Relm4-0.6.2/relm4-macros/src/widget_template.rs
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
use proc_macro2::{Span as Span2, TokenStream as TokenStream2};
|
||||||
|
use quote::quote;
|
||||||
|
use syn::{spanned::Spanned, Error, Ident, ImplItem, ItemImpl, Visibility};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
token_streams::{TokenStreams, TraitImplDetails},
|
||||||
|
widgets::ViewWidgets,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(crate) fn generate_tokens(vis: Option<Visibility>, mut item_impl: ItemImpl) -> TokenStream2 {
|
||||||
|
if item_impl.items.len() != 1 {
|
||||||
|
return Error::new(
|
||||||
|
item_impl.span(),
|
||||||
|
"Expected only one view macro and nothing else",
|
||||||
|
)
|
||||||
|
.into_compile_error();
|
||||||
|
}
|
||||||
|
|
||||||
|
let item = item_impl.items.pop().unwrap();
|
||||||
|
|
||||||
|
if let ImplItem::Macro(mac) = item {
|
||||||
|
if Some("view") == mac.mac.path.get_ident().map(|i| i.to_string()).as_deref() {
|
||||||
|
match syn::parse::<ViewWidgets>(mac.mac.tokens.into()) {
|
||||||
|
Ok(mut view_widgets) => {
|
||||||
|
view_widgets.mark_root_as_used();
|
||||||
|
|
||||||
|
let TokenStreams {
|
||||||
|
error,
|
||||||
|
init,
|
||||||
|
assign,
|
||||||
|
struct_fields,
|
||||||
|
return_fields,
|
||||||
|
..
|
||||||
|
} = view_widgets.generate_streams(
|
||||||
|
&TraitImplDetails {
|
||||||
|
vis: vis.clone(),
|
||||||
|
model_name: Ident::new("_", Span2::call_site()),
|
||||||
|
sender_name: Ident::new("sender", Span2::call_site()),
|
||||||
|
root_name: None,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
let view_output = quote! {
|
||||||
|
#init
|
||||||
|
#assign
|
||||||
|
{
|
||||||
|
#error
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let root_widget_type = view_widgets.root_type();
|
||||||
|
item_impl.items.push(ImplItem::Verbatim(quote! {
|
||||||
|
type Root = #root_widget_type;
|
||||||
|
}));
|
||||||
|
|
||||||
|
let root_name = view_widgets.root_name();
|
||||||
|
|
||||||
|
item_impl.items.push(ImplItem::Verbatim(quote! {
|
||||||
|
fn init() -> Self {
|
||||||
|
#view_output
|
||||||
|
Self {
|
||||||
|
#return_fields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
let type_name = &item_impl.self_ty;
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
#vis struct #type_name {
|
||||||
|
#struct_fields
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ::std::ops::Deref for #type_name {
|
||||||
|
type Target = #root_widget_type;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.#root_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#item_impl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => err.to_compile_error(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Error::new(mac.mac.path.span(), "Expected a view macro").into_compile_error()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Error::new(item.span(), "Expected a macro").into_compile_error()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,135 @@
|
|||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::{quote, quote_spanned, ToTokens};
|
||||||
|
use syn::spanned::Spanned;
|
||||||
|
use syn::Expr;
|
||||||
|
|
||||||
|
use crate::widgets::{AssignProperty, AssignPropertyAttr, PropertyName};
|
||||||
|
|
||||||
|
use super::AssignInfo;
|
||||||
|
|
||||||
|
impl AssignProperty {
|
||||||
|
pub(crate) fn conditional_assign_stream(
|
||||||
|
&self,
|
||||||
|
info: &mut AssignInfo<'_>,
|
||||||
|
p_name: &PropertyName,
|
||||||
|
init: bool,
|
||||||
|
) {
|
||||||
|
// If the code gen path is behind a conditional widgets, handle `watch` and `track` later.
|
||||||
|
// Normally, those would be initialized right away, but they might need access to
|
||||||
|
// variables from a pattern, for example `Some(variable)` so they are moved inside the
|
||||||
|
// match arm or if expression.
|
||||||
|
if !info.is_conditional
|
||||||
|
|| !matches!(
|
||||||
|
self.attr,
|
||||||
|
AssignPropertyAttr::Track { .. } | AssignPropertyAttr::Watch { .. }
|
||||||
|
)
|
||||||
|
{
|
||||||
|
self.assign_stream(info, p_name, init);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn assign_stream(
|
||||||
|
&self,
|
||||||
|
info: &mut AssignInfo<'_>,
|
||||||
|
p_name: &PropertyName,
|
||||||
|
init: bool,
|
||||||
|
) {
|
||||||
|
if init && self.attr.should_skip_init() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let assign_fn = p_name.assign_fn_stream(info);
|
||||||
|
let self_assign_args = p_name.assign_args_stream(info.widget_name);
|
||||||
|
let span = p_name.span();
|
||||||
|
|
||||||
|
let args = self.args.as_ref().map(|args| {
|
||||||
|
quote! {
|
||||||
|
, #args
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Destructure tuples
|
||||||
|
let assign = if let Expr::Tuple(tuple) = &self.expr {
|
||||||
|
tuple.elems.to_token_stream()
|
||||||
|
} else {
|
||||||
|
self.expr.to_token_stream()
|
||||||
|
};
|
||||||
|
|
||||||
|
let chain = self.chain.as_ref().map(|chain| {
|
||||||
|
quote_spanned! {
|
||||||
|
chain.span() => .#chain
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let (block_stream, unblock_stream) = if init || self.block_signals.is_empty() {
|
||||||
|
(None, None)
|
||||||
|
} else {
|
||||||
|
let mut block_stream = TokenStream2::default();
|
||||||
|
let mut unblock_stream = TokenStream2::default();
|
||||||
|
let gtk_import = crate::gtk_import();
|
||||||
|
|
||||||
|
let w_name = info.widget_name;
|
||||||
|
for signal_handler in &self.block_signals {
|
||||||
|
block_stream.extend(quote_spanned! {
|
||||||
|
signal_handler.span() =>
|
||||||
|
{
|
||||||
|
use relm4::WidgetRef;
|
||||||
|
#[allow(clippy::needless_borrow)]
|
||||||
|
#gtk_import::prelude::ObjectExt::block_signal(#w_name.widget_ref(), &#signal_handler);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
unblock_stream.extend(quote_spanned! {
|
||||||
|
signal_handler.span() =>
|
||||||
|
{
|
||||||
|
use relm4::WidgetRef;
|
||||||
|
#[allow(clippy::needless_borrow)]
|
||||||
|
#gtk_import::prelude::ObjectExt::unblock_signal(#w_name.widget_ref(), &#signal_handler);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
(Some(block_stream), Some(unblock_stream))
|
||||||
|
};
|
||||||
|
|
||||||
|
info.stream
|
||||||
|
.extend(match (self.optional_assign, self.iterative) {
|
||||||
|
(false, false) => {
|
||||||
|
quote_spanned! { span =>
|
||||||
|
#block_stream
|
||||||
|
#assign_fn(#self_assign_args #assign #args) #chain;
|
||||||
|
#unblock_stream
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(true, false) => {
|
||||||
|
quote_spanned! {
|
||||||
|
span => if let Some(__p_assign) = #assign {
|
||||||
|
#block_stream
|
||||||
|
#assign_fn(#self_assign_args __p_assign #args) #chain;
|
||||||
|
#unblock_stream
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(false, true) => {
|
||||||
|
quote_spanned! {
|
||||||
|
span =>
|
||||||
|
#block_stream
|
||||||
|
for __elem in #assign {
|
||||||
|
#assign_fn(#self_assign_args __elem #args) #chain;
|
||||||
|
}
|
||||||
|
#unblock_stream
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(true, true) => {
|
||||||
|
quote_spanned! {
|
||||||
|
span =>
|
||||||
|
#block_stream
|
||||||
|
for __elem in #assign {
|
||||||
|
if let Some(__p_assign) = __elem {
|
||||||
|
#assign_fn(#self_assign_args __p_assign #args) #chain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#unblock_stream
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
use quote::{quote, quote_spanned};
|
||||||
|
use syn::Ident;
|
||||||
|
|
||||||
|
use crate::widgets::{ConditionalBranches, ConditionalWidget, PropertyName};
|
||||||
|
|
||||||
|
use super::AssignInfo;
|
||||||
|
|
||||||
|
impl ConditionalWidget {
|
||||||
|
pub(super) fn assign_stream<'a>(
|
||||||
|
&'a self,
|
||||||
|
info: &mut AssignInfo<'a>,
|
||||||
|
p_name: &PropertyName,
|
||||||
|
sender_name: &'a Ident,
|
||||||
|
) {
|
||||||
|
let assign_fn = p_name.assign_fn_stream(info);
|
||||||
|
let self_assign_args = p_name.assign_args_stream(info.widget_name);
|
||||||
|
let span = p_name.span();
|
||||||
|
|
||||||
|
let args = self.args.as_ref().map(|args| {
|
||||||
|
quote! {
|
||||||
|
, #args
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let w_name = &self.name;
|
||||||
|
let assign_args = if let Some(assign_wrapper) = &self.assign_wrapper {
|
||||||
|
quote! { #assign_wrapper (&#w_name ) }
|
||||||
|
} else {
|
||||||
|
quote_spanned! { w_name.span() => &#w_name }
|
||||||
|
};
|
||||||
|
|
||||||
|
info.stream.extend(quote_spanned! {
|
||||||
|
span => #assign_fn(#self_assign_args #assign_args #args);
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut info = AssignInfo {
|
||||||
|
stream: info.stream,
|
||||||
|
widget_name: &self.name,
|
||||||
|
template_path: None,
|
||||||
|
is_conditional: true,
|
||||||
|
};
|
||||||
|
match &self.branches {
|
||||||
|
ConditionalBranches::If(if_branches) => {
|
||||||
|
for branch in if_branches {
|
||||||
|
let p_name = PropertyName::Ident(Ident::new("add_named", p_name.span()));
|
||||||
|
branch.widget.assign_stream(&mut info, &p_name, sender_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ConditionalBranches::Match((_, _, match_arms)) => {
|
||||||
|
for arm in match_arms {
|
||||||
|
let p_name = PropertyName::Ident(Ident::new("add_named", p_name.span()));
|
||||||
|
arm.widget.assign_stream(&mut info, &p_name, sender_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
37
Relm4-0.6.2/relm4-macros/src/widgets/gen/assign/mod.rs
Normal file
37
Relm4-0.6.2/relm4-macros/src/widgets/gen/assign/mod.rs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use syn::{punctuated::Punctuated, token, Ident};
|
||||||
|
|
||||||
|
use crate::widgets::{Property, PropertyType};
|
||||||
|
|
||||||
|
mod assign_property;
|
||||||
|
mod conditional_widget;
|
||||||
|
mod properties;
|
||||||
|
mod signal_handler;
|
||||||
|
mod widgets;
|
||||||
|
|
||||||
|
pub(crate) struct AssignInfo<'a> {
|
||||||
|
pub(crate) stream: &'a mut TokenStream2,
|
||||||
|
pub(crate) widget_name: &'a Ident,
|
||||||
|
pub(crate) template_path: Option<Punctuated<Ident, token::Dot>>,
|
||||||
|
pub(crate) is_conditional: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Property {
|
||||||
|
fn assign_stream<'a>(&'a self, info: &mut AssignInfo<'a>, sender_name: &'a Ident) {
|
||||||
|
match &self.ty {
|
||||||
|
PropertyType::Assign(assign) => {
|
||||||
|
assign.conditional_assign_stream(info, &self.name, true);
|
||||||
|
}
|
||||||
|
PropertyType::Widget(widget) => {
|
||||||
|
widget.assign_stream(info, &self.name, sender_name);
|
||||||
|
}
|
||||||
|
PropertyType::ConditionalWidget(cond_widget) => {
|
||||||
|
cond_widget.assign_stream(info, &self.name, sender_name);
|
||||||
|
}
|
||||||
|
PropertyType::SignalHandler(signal_handler) => {
|
||||||
|
signal_handler.connect_signals_stream(info, &self.name, sender_name);
|
||||||
|
}
|
||||||
|
PropertyType::ParseError(_) => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
use syn::Ident;
|
||||||
|
|
||||||
|
use crate::widgets::Properties;
|
||||||
|
|
||||||
|
use super::AssignInfo;
|
||||||
|
|
||||||
|
impl Properties {
|
||||||
|
pub(super) fn assign_stream<'a>(&'a self, info: &mut AssignInfo<'a>, sender_name: &'a Ident) {
|
||||||
|
for prop in &self.properties {
|
||||||
|
prop.assign_stream(info, sender_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,77 @@
|
|||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::{quote_spanned, ToTokens};
|
||||||
|
use syn::Expr;
|
||||||
|
use syn::{spanned::Spanned, Ident};
|
||||||
|
|
||||||
|
use crate::widgets::{PropertyName, SignalHandler, SignalHandlerVariant};
|
||||||
|
|
||||||
|
use super::AssignInfo;
|
||||||
|
|
||||||
|
impl SignalHandler {
|
||||||
|
pub(super) fn connect_signals_stream(
|
||||||
|
&self,
|
||||||
|
info: &mut AssignInfo<'_>,
|
||||||
|
p_name: &PropertyName,
|
||||||
|
sender_name: &Ident,
|
||||||
|
) {
|
||||||
|
let span = p_name.span();
|
||||||
|
let assign_fn = p_name.assign_fn_stream(info);
|
||||||
|
let self_assign_args = p_name.assign_args_stream(info.widget_name);
|
||||||
|
|
||||||
|
let (clone_stream, assignment) = match &self.inner {
|
||||||
|
SignalHandlerVariant::Expr(expr) => (
|
||||||
|
quote_spanned! { span =>
|
||||||
|
#[allow(clippy::redundant_clone)]
|
||||||
|
let sender = #sender_name.clone();
|
||||||
|
},
|
||||||
|
quote_spanned! {
|
||||||
|
span => move |_| {
|
||||||
|
sender.input(#expr)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SignalHandlerVariant::Closure(inner) => {
|
||||||
|
let mut clone_stream = TokenStream2::new();
|
||||||
|
if let Some(args) = &inner.args {
|
||||||
|
for arg in &args.inner {
|
||||||
|
if let Expr::Path(path) = arg {
|
||||||
|
if let Some(ident) = path.path.get_ident() {
|
||||||
|
// Just an ident was used. Simply clone it.
|
||||||
|
clone_stream.extend(quote_spanned! { arg.span() =>
|
||||||
|
#[allow(clippy::redundant_clone)]
|
||||||
|
#[allow(clippy::clone_on_copy)]
|
||||||
|
let #ident = #ident.clone();
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Allow more complex expressions such as `value = data.sender()`
|
||||||
|
clone_stream.extend(quote_spanned! { arg.span() =>
|
||||||
|
#[allow(clippy::redundant_clone)]
|
||||||
|
#[allow(clippy::clone_on_copy)]
|
||||||
|
let #arg;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(clone_stream, inner.closure.to_token_stream())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
info.stream
|
||||||
|
.extend(if let Some(signal_handler_id) = &self.handler_id {
|
||||||
|
quote_spanned! {
|
||||||
|
span => let #signal_handler_id = {
|
||||||
|
#clone_stream
|
||||||
|
#assign_fn(#self_assign_args #assignment)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote_spanned! {
|
||||||
|
span => {
|
||||||
|
#clone_stream
|
||||||
|
#assign_fn(#self_assign_args #assignment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
100
Relm4-0.6.2/relm4-macros/src/widgets/gen/assign/widgets.rs
Normal file
100
Relm4-0.6.2/relm4-macros/src/widgets/gen/assign/widgets.rs
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::{quote, quote_spanned};
|
||||||
|
use syn::spanned::Spanned;
|
||||||
|
use syn::Ident;
|
||||||
|
|
||||||
|
use crate::widgets::{PropertyName, ReturnedWidget, Widget, WidgetTemplateAttr};
|
||||||
|
|
||||||
|
use super::AssignInfo;
|
||||||
|
|
||||||
|
impl ReturnedWidget {
|
||||||
|
fn return_assign_tokens(&self) -> TokenStream2 {
|
||||||
|
let name = &self.name;
|
||||||
|
|
||||||
|
if let Some(ty) = &self.ty {
|
||||||
|
quote! {
|
||||||
|
let #name : #ty
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
let #name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget {
|
||||||
|
pub(crate) fn start_assign_stream<'a>(
|
||||||
|
&'a self,
|
||||||
|
stream: &'a mut TokenStream2,
|
||||||
|
sender_name: &'a Ident,
|
||||||
|
) {
|
||||||
|
let w_name = &self.name;
|
||||||
|
let mut info = AssignInfo {
|
||||||
|
stream,
|
||||||
|
widget_name: w_name,
|
||||||
|
template_path: None,
|
||||||
|
is_conditional: false,
|
||||||
|
};
|
||||||
|
self.properties.assign_stream(&mut info, sender_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn assign_stream<'a>(
|
||||||
|
&'a self,
|
||||||
|
info: &mut AssignInfo<'a>,
|
||||||
|
p_name: &PropertyName,
|
||||||
|
sender_name: &'a Ident,
|
||||||
|
) {
|
||||||
|
// Recursively generate code for properties
|
||||||
|
{
|
||||||
|
let template_path = (self.template_attr == WidgetTemplateAttr::TemplateChild)
|
||||||
|
.then_some(self.func.widget_template_path(info.widget_name, &self.name));
|
||||||
|
|
||||||
|
let mut info = AssignInfo {
|
||||||
|
stream: info.stream,
|
||||||
|
widget_name: &self.name,
|
||||||
|
template_path,
|
||||||
|
is_conditional: info.is_conditional,
|
||||||
|
};
|
||||||
|
self.properties.assign_stream(&mut info, sender_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Template children are already assigned by the template.
|
||||||
|
if self.template_attr != WidgetTemplateAttr::TemplateChild {
|
||||||
|
let assign_fn = p_name.assign_fn_stream(info);
|
||||||
|
let self_assign_args = p_name.assign_args_stream(info.widget_name);
|
||||||
|
let assign = self.widget_assignment();
|
||||||
|
let span = p_name.span();
|
||||||
|
|
||||||
|
let args = self.args.as_ref().map(|args| {
|
||||||
|
quote_spanned! {
|
||||||
|
args.span() => ,#args
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
info.stream.extend(if let Some(ret_widget) = &self.returned_widget {
|
||||||
|
let return_assign_stream = ret_widget.return_assign_tokens();
|
||||||
|
let unwrap = ret_widget.is_optional.then(|| quote! { .unwrap() });
|
||||||
|
quote_spanned! {
|
||||||
|
span => #return_assign_stream = #assign_fn(#self_assign_args #assign #args) #unwrap;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote_spanned! {
|
||||||
|
span => #assign_fn(#self_assign_args #assign #args);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(returned_widget) = &self.returned_widget {
|
||||||
|
let mut info = AssignInfo {
|
||||||
|
stream: info.stream,
|
||||||
|
widget_name: &returned_widget.name,
|
||||||
|
template_path: None,
|
||||||
|
is_conditional: info.is_conditional,
|
||||||
|
};
|
||||||
|
returned_widget
|
||||||
|
.properties
|
||||||
|
.assign_stream(&mut info, sender_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
207
Relm4-0.6.2/relm4-macros/src/widgets/gen/conditional_init.rs
Normal file
207
Relm4-0.6.2/relm4-macros/src/widgets/gen/conditional_init.rs
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::{quote, quote_spanned};
|
||||||
|
use syn::spanned::Spanned;
|
||||||
|
use syn::Ident;
|
||||||
|
|
||||||
|
use crate::widgets::{
|
||||||
|
AssignProperty, AssignPropertyAttr, ConditionalBranches, ConditionalWidget, MatchArm,
|
||||||
|
Properties, Property, PropertyName, PropertyType, ReturnedWidget, Widget,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::assign::AssignInfo;
|
||||||
|
|
||||||
|
impl Property {
|
||||||
|
fn conditional_init_stream(
|
||||||
|
&self,
|
||||||
|
stream: &mut TokenStream2,
|
||||||
|
w_name: &Ident,
|
||||||
|
model_name: &Ident,
|
||||||
|
is_conditional: bool,
|
||||||
|
) {
|
||||||
|
match &self.ty {
|
||||||
|
PropertyType::Assign(assign) => assign.conditional_init_stream(
|
||||||
|
stream,
|
||||||
|
&self.name,
|
||||||
|
w_name,
|
||||||
|
model_name,
|
||||||
|
is_conditional,
|
||||||
|
),
|
||||||
|
PropertyType::Widget(widget) => {
|
||||||
|
widget.conditional_init_stream(stream, model_name, is_conditional);
|
||||||
|
}
|
||||||
|
PropertyType::ConditionalWidget(cond_widget) => {
|
||||||
|
cond_widget.conditional_init_stream(stream, model_name);
|
||||||
|
}
|
||||||
|
PropertyType::SignalHandler(_) | PropertyType::ParseError(_) => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Properties {
|
||||||
|
fn conditional_init_stream(
|
||||||
|
&self,
|
||||||
|
stream: &mut TokenStream2,
|
||||||
|
w_name: &Ident,
|
||||||
|
model_name: &Ident,
|
||||||
|
is_conditional: bool,
|
||||||
|
) {
|
||||||
|
for prop in &self.properties {
|
||||||
|
prop.conditional_init_stream(stream, w_name, model_name, is_conditional);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget {
|
||||||
|
pub(crate) fn init_conditional_init_stream(
|
||||||
|
&self,
|
||||||
|
stream: &mut TokenStream2,
|
||||||
|
model_name: &Ident,
|
||||||
|
) {
|
||||||
|
self.conditional_init_stream(stream, model_name, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn conditional_init_stream(
|
||||||
|
&self,
|
||||||
|
stream: &mut TokenStream2,
|
||||||
|
model_name: &Ident,
|
||||||
|
is_conditional: bool,
|
||||||
|
) {
|
||||||
|
let w_name = &self.name;
|
||||||
|
self.properties
|
||||||
|
.conditional_init_stream(stream, w_name, model_name, is_conditional);
|
||||||
|
if let Some(returned_widget) = &self.returned_widget {
|
||||||
|
returned_widget.conditional_init_stream(stream, model_name, is_conditional);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConditionalWidget {
|
||||||
|
fn conditional_init_stream(&self, stream: &mut TokenStream2, model_name: &Ident) {
|
||||||
|
let brach_stream = match &self.branches {
|
||||||
|
ConditionalBranches::If(if_branches) => {
|
||||||
|
let mut stream = TokenStream2::new();
|
||||||
|
|
||||||
|
for (index, branch) in if_branches.iter().enumerate() {
|
||||||
|
let mut inner_update_stream = TokenStream2::new();
|
||||||
|
branch.widget.conditional_init_stream(
|
||||||
|
&mut inner_update_stream,
|
||||||
|
model_name,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
branch.update_stream(&mut stream, &inner_update_stream, index);
|
||||||
|
}
|
||||||
|
stream
|
||||||
|
}
|
||||||
|
ConditionalBranches::Match((match_token, expr, match_arms)) => {
|
||||||
|
let mut inner_tokens = TokenStream2::new();
|
||||||
|
|
||||||
|
for (index, match_arm) in match_arms.iter().enumerate() {
|
||||||
|
let mut inner_update_stream = TokenStream2::new();
|
||||||
|
match_arm.widget.conditional_init_stream(
|
||||||
|
&mut inner_update_stream,
|
||||||
|
model_name,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
let MatchArm {
|
||||||
|
pattern,
|
||||||
|
guard,
|
||||||
|
arrow,
|
||||||
|
..
|
||||||
|
} = match_arm;
|
||||||
|
|
||||||
|
let (guard_if, guard_expr) = if let Some((guard_if, guard_expr)) = guard {
|
||||||
|
(Some(guard_if), Some(guard_expr))
|
||||||
|
} else {
|
||||||
|
(None, None)
|
||||||
|
};
|
||||||
|
|
||||||
|
let index = index.to_string();
|
||||||
|
inner_tokens.extend(quote! {
|
||||||
|
#pattern #guard_if #guard_expr #arrow {
|
||||||
|
#inner_update_stream
|
||||||
|
#index
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
quote! {
|
||||||
|
#match_token #expr {
|
||||||
|
#inner_tokens
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let w_name = &self.name;
|
||||||
|
stream.extend(quote! {
|
||||||
|
let __current_page = "";
|
||||||
|
#w_name.set_visible_child_name(#brach_stream);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReturnedWidget {
|
||||||
|
fn conditional_init_stream(
|
||||||
|
&self,
|
||||||
|
stream: &mut TokenStream2,
|
||||||
|
model_name: &Ident,
|
||||||
|
is_conditional: bool,
|
||||||
|
) {
|
||||||
|
let w_name = &self.name;
|
||||||
|
self.properties
|
||||||
|
.conditional_init_stream(stream, w_name, model_name, is_conditional);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AssignProperty {
|
||||||
|
fn conditional_init_stream(
|
||||||
|
&self,
|
||||||
|
stream: &mut TokenStream2,
|
||||||
|
p_name: &PropertyName,
|
||||||
|
widget_name: &Ident,
|
||||||
|
model_name: &Ident,
|
||||||
|
is_conditional: bool,
|
||||||
|
) {
|
||||||
|
// Unconditional code is handled in the "normal" init stream
|
||||||
|
if is_conditional {
|
||||||
|
match &self.attr {
|
||||||
|
AssignPropertyAttr::None => (),
|
||||||
|
AssignPropertyAttr::Watch { skip_init } => {
|
||||||
|
if skip_init.is_none() {
|
||||||
|
let mut info = AssignInfo {
|
||||||
|
stream,
|
||||||
|
widget_name,
|
||||||
|
template_path: None,
|
||||||
|
is_conditional,
|
||||||
|
};
|
||||||
|
self.assign_stream(&mut info, p_name, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AssignPropertyAttr::Track {
|
||||||
|
track_expr,
|
||||||
|
paste_model,
|
||||||
|
skip_init,
|
||||||
|
} => {
|
||||||
|
if skip_init.is_none() {
|
||||||
|
let mut assign_stream = TokenStream2::new();
|
||||||
|
let mut info = AssignInfo {
|
||||||
|
stream: &mut assign_stream,
|
||||||
|
widget_name,
|
||||||
|
template_path: None,
|
||||||
|
is_conditional,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.assign_stream(&mut info, p_name, true);
|
||||||
|
let model = paste_model.then(|| model_name);
|
||||||
|
|
||||||
|
stream.extend(quote_spanned! {
|
||||||
|
track_expr.span() => if #model #track_expr {
|
||||||
|
#assign_stream
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,86 @@
|
|||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::quote;
|
||||||
|
|
||||||
|
use crate::widgets::{
|
||||||
|
ConditionalBranches, ConditionalWidget, Properties, Property, PropertyType, ReturnedWidget,
|
||||||
|
SignalHandler, Widget,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Property {
|
||||||
|
fn destructure_stream(&self, stream: &mut TokenStream2) {
|
||||||
|
match &self.ty {
|
||||||
|
PropertyType::Widget(widget) => widget.destructure_stream(stream),
|
||||||
|
PropertyType::SignalHandler(signal_handler) => {
|
||||||
|
signal_handler.destructure_stream(stream);
|
||||||
|
}
|
||||||
|
PropertyType::ConditionalWidget(cond_widget) => {
|
||||||
|
cond_widget.destructure_stream(stream);
|
||||||
|
}
|
||||||
|
PropertyType::Assign(_) | PropertyType::ParseError(_) => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Properties {
|
||||||
|
fn destructure_stream(&self, stream: &mut TokenStream2) {
|
||||||
|
for prop in &self.properties {
|
||||||
|
prop.destructure_stream(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget {
|
||||||
|
pub(crate) fn destructure_stream(&self, stream: &mut TokenStream2) {
|
||||||
|
if self.has_struct_field() {
|
||||||
|
let name = &self.name;
|
||||||
|
|
||||||
|
stream.extend(quote! { #name, });
|
||||||
|
}
|
||||||
|
|
||||||
|
self.properties.destructure_stream(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConditionalWidget {
|
||||||
|
fn destructure_stream(&self, stream: &mut TokenStream2) {
|
||||||
|
let name = &self.name;
|
||||||
|
|
||||||
|
stream.extend(quote! { #name, });
|
||||||
|
|
||||||
|
match &self.branches {
|
||||||
|
ConditionalBranches::If(if_branches) => {
|
||||||
|
for branch in if_branches {
|
||||||
|
branch.widget.destructure_stream(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ConditionalBranches::Match((_, _, match_arms)) => {
|
||||||
|
for arm in match_arms {
|
||||||
|
arm.widget.destructure_stream(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReturnedWidget {
|
||||||
|
pub(super) fn destructure_stream(&self, stream: &mut TokenStream2) {
|
||||||
|
if self.ty.is_some() {
|
||||||
|
let name = &self.name;
|
||||||
|
stream.extend(quote! {
|
||||||
|
#name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.properties.destructure_stream(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SignalHandler {
|
||||||
|
pub(super) fn destructure_stream(&self, stream: &mut TokenStream2) {
|
||||||
|
if let Some(signal_handler_id) = &self.handler_id {
|
||||||
|
stream.extend(quote! {
|
||||||
|
#signal_handler_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
77
Relm4-0.6.2/relm4-macros/src/widgets/gen/error.rs
Normal file
77
Relm4-0.6.2/relm4-macros/src/widgets/gen/error.rs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::{quote, ToTokens};
|
||||||
|
use syn::Ident;
|
||||||
|
|
||||||
|
use crate::widgets::{
|
||||||
|
ConditionalBranches, ConditionalWidget, ParseError, Properties, Property, PropertyType,
|
||||||
|
ReturnedWidget, Widget,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Property {
|
||||||
|
fn error_stream(&self, stream: &mut TokenStream2, w_name: &Ident) {
|
||||||
|
match &self.ty {
|
||||||
|
PropertyType::ParseError(error) => error.error_stream(stream, w_name),
|
||||||
|
PropertyType::SignalHandler(_) | PropertyType::Assign(_) => (),
|
||||||
|
PropertyType::Widget(widget) => widget.error_stream(stream),
|
||||||
|
PropertyType::ConditionalWidget(cond_widget) => cond_widget.error_stream(stream),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Properties {
|
||||||
|
fn error_stream(&self, stream: &mut TokenStream2, w_name: &Ident) {
|
||||||
|
for prop in &self.properties {
|
||||||
|
prop.error_stream(stream, w_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParseError {
|
||||||
|
fn error_stream(&self, stream: &mut TokenStream2, w_name: &Ident) {
|
||||||
|
match self {
|
||||||
|
ParseError::Ident((ident, tokens)) => stream.extend(quote! {
|
||||||
|
#tokens
|
||||||
|
#w_name.#ident ;
|
||||||
|
}),
|
||||||
|
ParseError::Path((path, tokens)) => stream.extend(quote! {
|
||||||
|
#tokens
|
||||||
|
#path ;
|
||||||
|
}),
|
||||||
|
ParseError::Generic(generic_error) => generic_error.to_tokens(stream),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget {
|
||||||
|
pub(crate) fn error_stream(&self, stream: &mut TokenStream2) {
|
||||||
|
let w_name = &self.name;
|
||||||
|
self.properties.error_stream(stream, w_name);
|
||||||
|
if let Some(returned_widget) = &self.returned_widget {
|
||||||
|
returned_widget.error_stream(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConditionalWidget {
|
||||||
|
fn error_stream(&self, stream: &mut TokenStream2) {
|
||||||
|
match &self.branches {
|
||||||
|
ConditionalBranches::If(if_branches) => {
|
||||||
|
for branch in if_branches {
|
||||||
|
branch.widget.error_stream(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ConditionalBranches::Match((_, _, match_arms)) => {
|
||||||
|
for arm in match_arms {
|
||||||
|
arm.widget.error_stream(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReturnedWidget {
|
||||||
|
fn error_stream(&self, stream: &mut TokenStream2) {
|
||||||
|
let w_name = &self.name;
|
||||||
|
self.properties.error_stream(stream, w_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
118
Relm4-0.6.2/relm4-macros/src/widgets/gen/init.rs
Normal file
118
Relm4-0.6.2/relm4-macros/src/widgets/gen/init.rs
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::{quote, quote_spanned};
|
||||||
|
|
||||||
|
use crate::widgets::{
|
||||||
|
ConditionalBranches, ConditionalWidget, Properties, Property, PropertyType, Widget, WidgetAttr,
|
||||||
|
WidgetTemplateAttr,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Property {
|
||||||
|
fn init_stream(&self, stream: &mut TokenStream2) {
|
||||||
|
match &self.ty {
|
||||||
|
PropertyType::Widget(widget) => {
|
||||||
|
widget.init_stream(stream);
|
||||||
|
}
|
||||||
|
PropertyType::ConditionalWidget(cond_widget) => {
|
||||||
|
cond_widget.init_stream(stream);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Properties {
|
||||||
|
fn init_stream(&self, stream: &mut TokenStream2) {
|
||||||
|
for prop in &self.properties {
|
||||||
|
prop.init_stream(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget {
|
||||||
|
pub(crate) fn init_stream(&self, stream: &mut TokenStream2) {
|
||||||
|
self.self_init_stream(stream);
|
||||||
|
self.other_init_stream(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn init_root_init_streams(
|
||||||
|
&self,
|
||||||
|
init_root_stream: &mut TokenStream2,
|
||||||
|
init_stream: &mut TokenStream2,
|
||||||
|
) {
|
||||||
|
// Init function as return value
|
||||||
|
init_root_stream.extend(match self.template_attr {
|
||||||
|
WidgetTemplateAttr::None | WidgetTemplateAttr::TemplateChild => {
|
||||||
|
self.func.func_token_stream()
|
||||||
|
}
|
||||||
|
WidgetTemplateAttr::Template => {
|
||||||
|
let widget_ty = &self.func.path;
|
||||||
|
quote! {
|
||||||
|
<#widget_ty as relm4::WidgetTemplate>::init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
self.other_init_stream(init_stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn self_init_stream(&self, stream: &mut TokenStream2) {
|
||||||
|
let mutability = &self.mutable;
|
||||||
|
let name = &self.name;
|
||||||
|
|
||||||
|
let ty = self.func.ty.as_ref().map(|ty| quote! {: #ty});
|
||||||
|
if self.attr == WidgetAttr::None {
|
||||||
|
match self.template_attr {
|
||||||
|
WidgetTemplateAttr::None => {
|
||||||
|
let func = self.func.func_token_stream();
|
||||||
|
stream.extend(quote! {
|
||||||
|
let #mutability #name #ty = #func;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
WidgetTemplateAttr::Template => {
|
||||||
|
let widget_ty = &self.func.path;
|
||||||
|
stream.extend(quote! {
|
||||||
|
let #mutability #name #ty = <#widget_ty as relm4::WidgetTemplate>::init();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Template children are already initialized by their template.
|
||||||
|
WidgetTemplateAttr::TemplateChild => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn other_init_stream(&self, stream: &mut TokenStream2) {
|
||||||
|
self.properties.init_stream(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConditionalWidget {
|
||||||
|
fn init_stream(&self, stream: &mut TokenStream2) {
|
||||||
|
let name = &self.name;
|
||||||
|
let gtk_import = crate::gtk_import();
|
||||||
|
|
||||||
|
stream.extend(quote_spanned! {
|
||||||
|
name.span() =>
|
||||||
|
let #name = #gtk_import::Stack::default();
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(transition) = &self.transition {
|
||||||
|
stream.extend(quote_spanned! {
|
||||||
|
transition.span() =>
|
||||||
|
#name.set_transition_type(#gtk_import::StackTransitionType:: #transition);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
match &self.branches {
|
||||||
|
ConditionalBranches::If(if_branches) => {
|
||||||
|
for branch in if_branches {
|
||||||
|
branch.widget.init_stream(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ConditionalBranches::Match((_, _, match_arms)) => {
|
||||||
|
for arm in match_arms {
|
||||||
|
arm.widget.init_stream(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
42
Relm4-0.6.2/relm4-macros/src/widgets/gen/mod.rs
Normal file
42
Relm4-0.6.2/relm4-macros/src/widgets/gen/mod.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::{quote, quote_spanned};
|
||||||
|
use syn::{spanned::Spanned, token};
|
||||||
|
|
||||||
|
use super::{PropertyName, ReturnedWidget, Widget, WidgetTemplateAttr};
|
||||||
|
|
||||||
|
/// Utility methods and functions.
|
||||||
|
mod util;
|
||||||
|
|
||||||
|
/// Generate struct fields.
|
||||||
|
mod struct_fields;
|
||||||
|
|
||||||
|
/// Fields of the returned widget struct.
|
||||||
|
mod return_fields;
|
||||||
|
|
||||||
|
mod assign;
|
||||||
|
mod conditional_init;
|
||||||
|
mod destructure_fields;
|
||||||
|
mod error;
|
||||||
|
mod init;
|
||||||
|
mod update_view;
|
||||||
|
|
||||||
|
impl Widget {
|
||||||
|
pub(super) fn widget_assignment(&self) -> TokenStream2 {
|
||||||
|
let w_name = &self.name;
|
||||||
|
|
||||||
|
let ref_token = &self.ref_token;
|
||||||
|
let deref_token = &self.deref_token;
|
||||||
|
let template_deref =
|
||||||
|
(self.template_attr == WidgetTemplateAttr::Template).then(token::Star::default);
|
||||||
|
|
||||||
|
let out_stream = quote! { #ref_token #deref_token #template_deref #w_name };
|
||||||
|
|
||||||
|
if let Some(wrapper) = &self.assign_wrapper {
|
||||||
|
quote_spanned! {
|
||||||
|
wrapper.span() => #wrapper(#out_stream)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
out_stream
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
80
Relm4-0.6.2/relm4-macros/src/widgets/gen/return_fields.rs
Normal file
80
Relm4-0.6.2/relm4-macros/src/widgets/gen/return_fields.rs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::quote;
|
||||||
|
|
||||||
|
use crate::widgets::{
|
||||||
|
ConditionalBranches, ConditionalWidget, Properties, Property, PropertyType, ReturnedWidget,
|
||||||
|
SignalHandler, Widget, WidgetAttr,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Property {
|
||||||
|
fn return_stream(&self, stream: &mut TokenStream2) {
|
||||||
|
match &self.ty {
|
||||||
|
PropertyType::Widget(widget) => widget.return_stream(stream),
|
||||||
|
PropertyType::SignalHandler(signal_handler) => signal_handler.return_stream(stream),
|
||||||
|
PropertyType::ConditionalWidget(cond_widget) => cond_widget.return_stream(stream),
|
||||||
|
PropertyType::Assign(_) | PropertyType::ParseError(_) => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Properties {
|
||||||
|
fn return_stream(&self, stream: &mut TokenStream2) {
|
||||||
|
for prop in &self.properties {
|
||||||
|
prop.return_stream(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget {
|
||||||
|
pub(crate) fn return_stream(&self, stream: &mut TokenStream2) {
|
||||||
|
if self.has_struct_field() {
|
||||||
|
let name = &self.name;
|
||||||
|
|
||||||
|
stream.extend(if self.attr == WidgetAttr::LocalRef {
|
||||||
|
// The local reference must be cloned first
|
||||||
|
quote! { #name: #name.clone(), }
|
||||||
|
} else {
|
||||||
|
quote! { #name, }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.properties.return_stream(stream);
|
||||||
|
if let Some(returned_widget) = &self.returned_widget {
|
||||||
|
returned_widget.return_stream(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConditionalWidget {
|
||||||
|
fn return_stream(&self, stream: &mut TokenStream2) {
|
||||||
|
let name = &self.name;
|
||||||
|
|
||||||
|
stream.extend(quote! { #name, });
|
||||||
|
|
||||||
|
match &self.branches {
|
||||||
|
ConditionalBranches::If(if_branches) => {
|
||||||
|
for branch in if_branches {
|
||||||
|
branch.widget.return_stream(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ConditionalBranches::Match((_, _, match_arms)) => {
|
||||||
|
for arm in match_arms {
|
||||||
|
arm.widget.return_stream(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReturnedWidget {
|
||||||
|
fn return_stream(&self, stream: &mut TokenStream2) {
|
||||||
|
self.destructure_stream(stream);
|
||||||
|
self.properties.return_stream(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SignalHandler {
|
||||||
|
fn return_stream(&self, stream: &mut TokenStream2) {
|
||||||
|
self.destructure_stream(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
117
Relm4-0.6.2/relm4-macros/src/widgets/gen/struct_fields.rs
Normal file
117
Relm4-0.6.2/relm4-macros/src/widgets/gen/struct_fields.rs
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::{quote, quote_spanned};
|
||||||
|
use syn::Visibility;
|
||||||
|
|
||||||
|
use super::{ReturnedWidget, Widget};
|
||||||
|
use crate::widgets::{
|
||||||
|
ConditionalBranches, ConditionalWidget, Properties, Property, PropertyType, SignalHandler,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Property {
|
||||||
|
fn struct_fields_stream(&self, stream: &mut TokenStream2, vis: &Option<Visibility>) {
|
||||||
|
match &self.ty {
|
||||||
|
PropertyType::Widget(widget) => widget.struct_fields_stream(stream, vis),
|
||||||
|
PropertyType::SignalHandler(signal_handler) => {
|
||||||
|
signal_handler.struct_fields_stream(stream, vis);
|
||||||
|
}
|
||||||
|
PropertyType::ConditionalWidget(cond_widget) => {
|
||||||
|
cond_widget.struct_fields_stream(stream, vis);
|
||||||
|
}
|
||||||
|
PropertyType::Assign(_) | PropertyType::ParseError(_) => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Properties {
|
||||||
|
fn struct_fields_stream(&self, stream: &mut TokenStream2, vis: &Option<Visibility>) {
|
||||||
|
for prop in &self.properties {
|
||||||
|
prop.struct_fields_stream(stream, vis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget {
|
||||||
|
pub(crate) fn struct_fields_stream(&self, stream: &mut TokenStream2, vis: &Option<Visibility>) {
|
||||||
|
if self.has_struct_field() {
|
||||||
|
let name = &self.name;
|
||||||
|
let ty = self.func_type_token_stream();
|
||||||
|
|
||||||
|
stream.extend(if let Some(docs) = &self.doc_attr {
|
||||||
|
quote! {
|
||||||
|
#[doc = #docs]
|
||||||
|
#vis #name: #ty,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
#vis #name: #ty,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.properties.struct_fields_stream(stream, vis);
|
||||||
|
if let Some(returned_widget) = &self.returned_widget {
|
||||||
|
returned_widget.struct_fields_stream(stream, vis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConditionalWidget {
|
||||||
|
fn struct_fields_stream(&self, stream: &mut TokenStream2, vis: &Option<Visibility>) {
|
||||||
|
let name = &self.name;
|
||||||
|
let gtk_import = crate::gtk_import();
|
||||||
|
|
||||||
|
stream.extend(if let Some(docs) = &self.doc_attr {
|
||||||
|
quote_spanned! {
|
||||||
|
name.span() =>
|
||||||
|
#[doc = #docs]
|
||||||
|
#vis #name: #gtk_import::Stack,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote_spanned! {
|
||||||
|
name.span() =>
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
#vis #name: #gtk_import::Stack,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
match &self.branches {
|
||||||
|
ConditionalBranches::If(if_branches) => {
|
||||||
|
for branch in if_branches {
|
||||||
|
branch.widget.struct_fields_stream(stream, vis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ConditionalBranches::Match((_, _, match_arms)) => {
|
||||||
|
for arm in match_arms {
|
||||||
|
arm.widget.struct_fields_stream(stream, vis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReturnedWidget {
|
||||||
|
fn struct_fields_stream(&self, stream: &mut TokenStream2, vis: &Option<Visibility>) {
|
||||||
|
if let Some(ty) = &self.ty {
|
||||||
|
let name = &self.name;
|
||||||
|
stream.extend(quote! {
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
#vis #name: #ty,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
self.properties.struct_fields_stream(stream, vis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SignalHandler {
|
||||||
|
fn struct_fields_stream(&self, stream: &mut TokenStream2, vis: &Option<Visibility>) {
|
||||||
|
if let Some(signal_handler_id) = &self.handler_id {
|
||||||
|
let gtk_import = crate::gtk_import();
|
||||||
|
stream.extend(quote_spanned! {
|
||||||
|
signal_handler_id.span() =>
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
#vis #signal_handler_id: #gtk_import::glib::signal::SignalHandlerId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
231
Relm4-0.6.2/relm4-macros/src/widgets/gen/update_view.rs
Normal file
231
Relm4-0.6.2/relm4-macros/src/widgets/gen/update_view.rs
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::{quote, quote_spanned};
|
||||||
|
use syn::{punctuated::Punctuated, token, Ident};
|
||||||
|
|
||||||
|
use crate::widgets::{
|
||||||
|
AssignProperty, AssignPropertyAttr, ConditionalBranches, ConditionalWidget, MatchArm,
|
||||||
|
Properties, Property, PropertyName, PropertyType, ReturnedWidget, Widget, WidgetTemplateAttr,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::assign::AssignInfo;
|
||||||
|
|
||||||
|
impl Property {
|
||||||
|
fn update_view_stream(
|
||||||
|
&self,
|
||||||
|
stream: &mut TokenStream2,
|
||||||
|
widget_name: &Ident,
|
||||||
|
template_path: Option<Punctuated<Ident, token::Dot>>,
|
||||||
|
model_name: &Ident,
|
||||||
|
conditional_branch: bool,
|
||||||
|
) {
|
||||||
|
match &self.ty {
|
||||||
|
PropertyType::Assign(assign) => assign.update_view_stream(
|
||||||
|
stream,
|
||||||
|
&self.name,
|
||||||
|
widget_name,
|
||||||
|
template_path,
|
||||||
|
model_name,
|
||||||
|
conditional_branch,
|
||||||
|
),
|
||||||
|
PropertyType::Widget(widget) => {
|
||||||
|
widget.update_view_stream(
|
||||||
|
stream,
|
||||||
|
Some(widget_name),
|
||||||
|
model_name,
|
||||||
|
conditional_branch,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
PropertyType::ConditionalWidget(cond_widget) => {
|
||||||
|
cond_widget.update_view_stream(stream, model_name);
|
||||||
|
}
|
||||||
|
PropertyType::SignalHandler(_) | PropertyType::ParseError(_) => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Properties {
|
||||||
|
fn update_view_stream(
|
||||||
|
&self,
|
||||||
|
stream: &mut TokenStream2,
|
||||||
|
widget_name: &Ident,
|
||||||
|
template_path: Option<Punctuated<Ident, token::Dot>>,
|
||||||
|
model_name: &Ident,
|
||||||
|
conditional_branch: bool,
|
||||||
|
) {
|
||||||
|
for prop in &self.properties {
|
||||||
|
prop.update_view_stream(
|
||||||
|
stream,
|
||||||
|
widget_name,
|
||||||
|
template_path.clone(),
|
||||||
|
model_name,
|
||||||
|
conditional_branch,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget {
|
||||||
|
pub(crate) fn init_update_view_stream(&self, stream: &mut TokenStream2, model_name: &Ident) {
|
||||||
|
self.update_view_stream(stream, None, model_name, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_view_stream(
|
||||||
|
&self,
|
||||||
|
stream: &mut TokenStream2,
|
||||||
|
parent_widget_name: Option<&Ident>,
|
||||||
|
model_name: &Ident,
|
||||||
|
conditional_branch: bool,
|
||||||
|
) {
|
||||||
|
let widget_name = &self.name;
|
||||||
|
let template_path = if self.template_attr == WidgetTemplateAttr::TemplateChild {
|
||||||
|
parent_widget_name.map(|parent_widget_name| {
|
||||||
|
self.func
|
||||||
|
.widget_template_path(parent_widget_name, &self.name)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
self.properties.update_view_stream(
|
||||||
|
stream,
|
||||||
|
widget_name,
|
||||||
|
template_path,
|
||||||
|
model_name,
|
||||||
|
conditional_branch,
|
||||||
|
);
|
||||||
|
if let Some(returned_widget) = &self.returned_widget {
|
||||||
|
returned_widget.update_view_stream(stream, model_name, conditional_branch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConditionalWidget {
|
||||||
|
fn update_view_stream(&self, stream: &mut TokenStream2, model_name: &Ident) {
|
||||||
|
let brach_stream = match &self.branches {
|
||||||
|
ConditionalBranches::If(if_branches) => {
|
||||||
|
let mut stream = TokenStream2::new();
|
||||||
|
|
||||||
|
for (index, branch) in if_branches.iter().enumerate() {
|
||||||
|
let mut inner_update_stream = TokenStream2::new();
|
||||||
|
branch.widget.update_view_stream(
|
||||||
|
&mut inner_update_stream,
|
||||||
|
None,
|
||||||
|
model_name,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
branch.update_stream(&mut stream, &inner_update_stream, index);
|
||||||
|
}
|
||||||
|
stream
|
||||||
|
}
|
||||||
|
ConditionalBranches::Match((match_token, expr, match_arms)) => {
|
||||||
|
let mut inner_tokens = TokenStream2::new();
|
||||||
|
for (index, match_arm) in match_arms.iter().enumerate() {
|
||||||
|
let mut inner_update_stream = TokenStream2::new();
|
||||||
|
match_arm.widget.update_view_stream(
|
||||||
|
&mut inner_update_stream,
|
||||||
|
None,
|
||||||
|
model_name,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
let MatchArm {
|
||||||
|
pattern,
|
||||||
|
guard,
|
||||||
|
arrow,
|
||||||
|
..
|
||||||
|
} = match_arm;
|
||||||
|
let (guard_if, guard_expr) = if let Some((guard_if, guard_expr)) = guard {
|
||||||
|
(Some(guard_if), Some(guard_expr))
|
||||||
|
} else {
|
||||||
|
(None, None)
|
||||||
|
};
|
||||||
|
|
||||||
|
let index = index.to_string();
|
||||||
|
inner_tokens.extend(quote! {
|
||||||
|
#pattern #guard_if #guard_expr #arrow {
|
||||||
|
let __page_active: bool = (__current_page == #index);
|
||||||
|
#inner_update_stream
|
||||||
|
#index
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
quote! {
|
||||||
|
#match_token #expr {
|
||||||
|
#inner_tokens
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let w_name = &self.name;
|
||||||
|
stream.extend(quote_spanned! {
|
||||||
|
w_name.span() =>
|
||||||
|
let __current_page = #w_name.visible_child_name().map_or("".to_string(), |s| s.as_str().to_string());
|
||||||
|
#w_name.set_visible_child_name(#brach_stream);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReturnedWidget {
|
||||||
|
fn update_view_stream(
|
||||||
|
&self,
|
||||||
|
stream: &mut TokenStream2,
|
||||||
|
model_name: &Ident,
|
||||||
|
conditional_branch: bool,
|
||||||
|
) {
|
||||||
|
let w_name = &self.name;
|
||||||
|
self.properties
|
||||||
|
.update_view_stream(stream, w_name, None, model_name, conditional_branch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AssignProperty {
|
||||||
|
fn update_view_stream(
|
||||||
|
&self,
|
||||||
|
stream: &mut TokenStream2,
|
||||||
|
p_name: &PropertyName,
|
||||||
|
widget_name: &Ident,
|
||||||
|
template_path: Option<Punctuated<Ident, token::Dot>>,
|
||||||
|
model_name: &Ident,
|
||||||
|
conditional_branch: bool,
|
||||||
|
) {
|
||||||
|
match &self.attr {
|
||||||
|
AssignPropertyAttr::None => (),
|
||||||
|
AssignPropertyAttr::Watch { .. } => {
|
||||||
|
let mut info = AssignInfo {
|
||||||
|
stream,
|
||||||
|
widget_name,
|
||||||
|
template_path,
|
||||||
|
is_conditional: false,
|
||||||
|
};
|
||||||
|
self.assign_stream(&mut info, p_name, false);
|
||||||
|
}
|
||||||
|
AssignPropertyAttr::Track {
|
||||||
|
track_expr,
|
||||||
|
paste_model,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let mut assign_stream = TokenStream2::new();
|
||||||
|
let mut info = AssignInfo {
|
||||||
|
stream: &mut assign_stream,
|
||||||
|
widget_name,
|
||||||
|
template_path: None,
|
||||||
|
is_conditional: false,
|
||||||
|
};
|
||||||
|
self.assign_stream(&mut info, p_name, false);
|
||||||
|
let model = paste_model.then(|| model_name);
|
||||||
|
let page_switch = conditional_branch.then(|| {
|
||||||
|
quote_spanned! {
|
||||||
|
p_name.span() =>
|
||||||
|
!__page_active ||
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.extend(quote! {
|
||||||
|
if #page_switch (#model #track_expr) {
|
||||||
|
#assign_stream
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
use crate::widgets::{AssignPropertyAttr, Properties, PropertyType, Widget, WidgetTemplateAttr};
|
||||||
|
|
||||||
|
impl Widget {
|
||||||
|
/// Don't generate any fields if the widget wasn't named by the user and
|
||||||
|
/// isn't used for any property updates either.
|
||||||
|
pub(crate) fn has_struct_field(&self) -> bool {
|
||||||
|
match self.template_attr {
|
||||||
|
WidgetTemplateAttr::None => {
|
||||||
|
self.name_assigned_by_user || self.properties.are_properties_updated()
|
||||||
|
}
|
||||||
|
WidgetTemplateAttr::Template => true,
|
||||||
|
WidgetTemplateAttr::TemplateChild => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Properties {
|
||||||
|
pub(crate) fn are_properties_updated(&self) -> bool {
|
||||||
|
// Is there any property with watch or track attribute?
|
||||||
|
self.properties.iter().any(|prop| match &prop.ty {
|
||||||
|
PropertyType::Assign(assign_prop) => matches!(
|
||||||
|
&assign_prop.attr,
|
||||||
|
AssignPropertyAttr::Track { .. } | AssignPropertyAttr::Watch { .. }
|
||||||
|
),
|
||||||
|
_ => false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
33
Relm4-0.6.2/relm4-macros/src/widgets/gen/util/if_branch.rs
Normal file
33
Relm4-0.6.2/relm4-macros/src/widgets/gen/util/if_branch.rs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::quote;
|
||||||
|
|
||||||
|
use crate::widgets::{IfBranch, IfCondition};
|
||||||
|
|
||||||
|
impl IfBranch {
|
||||||
|
pub(crate) fn update_stream(
|
||||||
|
&self,
|
||||||
|
stream: &mut TokenStream2,
|
||||||
|
inner_update_tokens: &TokenStream2,
|
||||||
|
index: usize,
|
||||||
|
) {
|
||||||
|
let index = index.to_string();
|
||||||
|
stream.extend(match &self.cond {
|
||||||
|
IfCondition::If(if_token, expr) => quote! {
|
||||||
|
#if_token #expr
|
||||||
|
},
|
||||||
|
IfCondition::ElseIf(else_token, if_token, expr) => quote! {
|
||||||
|
#else_token #if_token #expr
|
||||||
|
},
|
||||||
|
IfCondition::Else(else_token) => quote! {
|
||||||
|
#else_token
|
||||||
|
},
|
||||||
|
});
|
||||||
|
stream.extend(quote! {
|
||||||
|
{
|
||||||
|
let __page_active: bool = (__current_page == #index);
|
||||||
|
#inner_update_tokens
|
||||||
|
#index
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
5
Relm4-0.6.2/relm4-macros/src/widgets/gen/util/mod.rs
Normal file
5
Relm4-0.6.2/relm4-macros/src/widgets/gen/util/mod.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
mod has_struct_field;
|
||||||
|
mod if_branch;
|
||||||
|
mod property_name;
|
||||||
|
mod widget;
|
||||||
|
mod widget_func;
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::{quote, quote_spanned, ToTokens};
|
||||||
|
use syn::Ident;
|
||||||
|
|
||||||
|
use crate::widgets::gen::{assign::AssignInfo, PropertyName};
|
||||||
|
|
||||||
|
impl PropertyName {
|
||||||
|
pub(crate) fn assign_fn_stream(&self, info: &mut AssignInfo<'_>) -> TokenStream2 {
|
||||||
|
let AssignInfo {
|
||||||
|
widget_name,
|
||||||
|
template_path,
|
||||||
|
..
|
||||||
|
} = info;
|
||||||
|
let widget_name = if let Some(template_path) = template_path {
|
||||||
|
quote! { #template_path }
|
||||||
|
} else {
|
||||||
|
quote! { #widget_name }
|
||||||
|
};
|
||||||
|
|
||||||
|
match self {
|
||||||
|
PropertyName::Ident(ident) => {
|
||||||
|
quote! { #widget_name.#ident }
|
||||||
|
}
|
||||||
|
PropertyName::Path(path) => path.to_token_stream(),
|
||||||
|
PropertyName::RelmContainerExtAssign(span) => {
|
||||||
|
quote_spanned! { *span => #widget_name.container_add }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn assign_args_stream(&self, w_name: &Ident) -> Option<TokenStream2> {
|
||||||
|
match self {
|
||||||
|
PropertyName::RelmContainerExtAssign(_) | PropertyName::Ident(_) => None,
|
||||||
|
PropertyName::Path(_) => Some(quote_spanned! { w_name.span() => & #w_name, }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
Relm4-0.6.2/relm4-macros/src/widgets/gen/util/widget.rs
Normal file
14
Relm4-0.6.2/relm4-macros/src/widgets/gen/util/widget.rs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
use crate::widgets::ViewWidgets;
|
||||||
|
|
||||||
|
impl ViewWidgets {
|
||||||
|
/// Get a mutable reference to the root widget
|
||||||
|
pub(crate) fn mark_root_as_used(&mut self) {
|
||||||
|
if let Some(root_widget) = self
|
||||||
|
.top_level_widgets
|
||||||
|
.iter_mut()
|
||||||
|
.find(|w| w.root_attr.is_some())
|
||||||
|
{
|
||||||
|
root_widget.inner.name_assigned_by_user = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
112
Relm4-0.6.2/relm4-macros/src/widgets/gen/util/widget_func.rs
Normal file
112
Relm4-0.6.2/relm4-macros/src/widgets/gen/util/widget_func.rs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::{quote, quote_spanned, ToTokens};
|
||||||
|
use syn::punctuated::Punctuated;
|
||||||
|
use syn::{spanned::Spanned, Ident};
|
||||||
|
use syn::{token, Error};
|
||||||
|
|
||||||
|
use crate::widgets::{Widget, WidgetFunc};
|
||||||
|
|
||||||
|
impl Widget {
|
||||||
|
/// Get tokens for the widget's type.
|
||||||
|
pub(crate) fn func_type_token_stream(&self) -> TokenStream2 {
|
||||||
|
let is_local = self.attr.is_local_attr();
|
||||||
|
let func = &self.func;
|
||||||
|
let path = &self.func.path;
|
||||||
|
let mut tokens = TokenStream2::new();
|
||||||
|
|
||||||
|
// If type was specified, use it
|
||||||
|
let (type_segments, num_of_segments) = if let Some(ty) = &func.ty {
|
||||||
|
return ty.to_token_stream();
|
||||||
|
} else if is_local {
|
||||||
|
return Error::new(func.span().unwrap().into(),
|
||||||
|
format!("You need to specify the type of the local variable. Use this instead: {} -> Type {{ ...",
|
||||||
|
self.name)).into_compile_error();
|
||||||
|
} else if func.args.is_some() {
|
||||||
|
// If for example gtk::Box::new() was used, ignore ::new()
|
||||||
|
// and use gtk::Box as type.
|
||||||
|
let len = path.segments.len();
|
||||||
|
if len == 0 {
|
||||||
|
unreachable!("Path can't be empty");
|
||||||
|
} else if len == 1 {
|
||||||
|
return Error::new(func.span().unwrap().into(),
|
||||||
|
format!("You need to specify a type of your function. Use this instead: {}() -> Type {{ ...",
|
||||||
|
path.to_token_stream())).into_compile_error();
|
||||||
|
} else {
|
||||||
|
(&path.segments, len - 1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(&path.segments, path.segments.len())
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut seg_iter = type_segments.iter().take(num_of_segments);
|
||||||
|
let first = if let Some(first) = seg_iter.next() {
|
||||||
|
first
|
||||||
|
} else {
|
||||||
|
return Error::new(
|
||||||
|
func.span().unwrap().into(),
|
||||||
|
"No path segments in WidgetFunc.",
|
||||||
|
)
|
||||||
|
.into_compile_error();
|
||||||
|
};
|
||||||
|
tokens.extend(first.to_token_stream());
|
||||||
|
|
||||||
|
for segment in seg_iter {
|
||||||
|
tokens.extend(quote! {::});
|
||||||
|
tokens.extend(segment.to_token_stream());
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WidgetFunc {
|
||||||
|
/// Get the tokens of the widget's function.
|
||||||
|
pub(crate) fn func_token_stream(&self) -> TokenStream2 {
|
||||||
|
let WidgetFunc {
|
||||||
|
path,
|
||||||
|
args,
|
||||||
|
method_chain,
|
||||||
|
..
|
||||||
|
} = &self;
|
||||||
|
|
||||||
|
let mut stream = if let Some(args) = args {
|
||||||
|
quote! { #path(#args) }
|
||||||
|
} else if method_chain.is_some() {
|
||||||
|
path.to_token_stream()
|
||||||
|
} else {
|
||||||
|
quote_spanned! {
|
||||||
|
path.span() => #path::default()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(method_chain) = method_chain {
|
||||||
|
stream.extend(quote! {
|
||||||
|
.#method_chain
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stream
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn widget_template_path(
|
||||||
|
&self,
|
||||||
|
template_widget_name: &Ident,
|
||||||
|
widget_name: &Ident,
|
||||||
|
) -> Punctuated<Ident, token::Dot> {
|
||||||
|
let mut template_path = Punctuated::new();
|
||||||
|
template_path.push(template_widget_name.clone());
|
||||||
|
template_path.push(widget_name.clone());
|
||||||
|
if let Some(chain) = &self.method_chain {
|
||||||
|
for method in chain {
|
||||||
|
if method.turbofish.is_some() || method.args.is_some() {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
template_path.push(method.ident.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
template_path
|
||||||
|
} else {
|
||||||
|
template_path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
218
Relm4-0.6.2/relm4-macros/src/widgets/mod.rs
Normal file
218
Relm4-0.6.2/relm4-macros/src/widgets/mod.rs
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
use proc_macro2::{Span as Span2, TokenStream as TokenStream2};
|
||||||
|
use syn::punctuated::Punctuated;
|
||||||
|
use syn::token::{Else, FatArrow, If, Match, Mut};
|
||||||
|
use syn::{token, AngleBracketedGenericArguments, Expr, ExprClosure, Ident, Pat, Path, Type};
|
||||||
|
|
||||||
|
use crate::args::Args;
|
||||||
|
|
||||||
|
mod gen;
|
||||||
|
mod parse;
|
||||||
|
mod parse_util;
|
||||||
|
mod span;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(super) struct ViewWidgets {
|
||||||
|
pub(super) span: Span2,
|
||||||
|
pub(super) top_level_widgets: Vec<TopLevelWidget>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(super) struct TopLevelWidget {
|
||||||
|
pub(super) root_attr: Option<Ident>,
|
||||||
|
pub(super) inner: Widget,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum PropertyType {
|
||||||
|
Assign(AssignProperty),
|
||||||
|
SignalHandler(SignalHandler),
|
||||||
|
Widget(Widget),
|
||||||
|
ConditionalWidget(ConditionalWidget),
|
||||||
|
ParseError(ParseError),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum ParseError {
|
||||||
|
Ident((Ident, TokenStream2)),
|
||||||
|
Path((Path, TokenStream2)),
|
||||||
|
Generic(TokenStream2),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum AssignPropertyAttr {
|
||||||
|
None,
|
||||||
|
Watch {
|
||||||
|
skip_init: Option<Ident>,
|
||||||
|
},
|
||||||
|
Track {
|
||||||
|
track_expr: TokenStream2,
|
||||||
|
skip_init: Option<Ident>,
|
||||||
|
paste_model: bool,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct AssignProperty {
|
||||||
|
attr: AssignPropertyAttr,
|
||||||
|
/// Optional arguments like param_name[arg1, arg2, ...]
|
||||||
|
args: Option<Args<Expr>>,
|
||||||
|
expr: Expr,
|
||||||
|
/// Assign with an ?
|
||||||
|
optional_assign: bool,
|
||||||
|
/// Iterate through elements to generate tokens
|
||||||
|
iterative: bool,
|
||||||
|
block_signals: Vec<Ident>,
|
||||||
|
chain: Option<Box<Expr>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct SignalHandler {
|
||||||
|
inner: SignalHandlerVariant,
|
||||||
|
handler_id: Option<Ident>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum SignalHandlerVariant {
|
||||||
|
Expr(Expr),
|
||||||
|
Closure(ClosureSignalHandler),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ClosureSignalHandler {
|
||||||
|
closure: ExprClosure,
|
||||||
|
args: Option<Args<Expr>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum PropertyName {
|
||||||
|
Ident(Ident),
|
||||||
|
Path(Path),
|
||||||
|
RelmContainerExtAssign(Span2),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Property {
|
||||||
|
/// Either a path or just an ident
|
||||||
|
name: PropertyName,
|
||||||
|
ty: PropertyType,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct Properties {
|
||||||
|
properties: Vec<Property>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The function that initializes the widget.
|
||||||
|
///
|
||||||
|
/// This might be a real function or just something like `gtk::Label`.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct WidgetFunc {
|
||||||
|
path: Path,
|
||||||
|
args: Option<Punctuated<Expr, token::Comma>>,
|
||||||
|
method_chain: Option<Punctuated<WidgetFuncMethod, token::Dot>>,
|
||||||
|
ty: Option<Box<Type>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct WidgetFuncMethod {
|
||||||
|
ident: Ident,
|
||||||
|
turbofish: Option<AngleBracketedGenericArguments>,
|
||||||
|
args: Option<Punctuated<Expr, token::Comma>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(super) struct Widget {
|
||||||
|
doc_attr: Option<TokenStream2>,
|
||||||
|
attr: WidgetAttr,
|
||||||
|
template_attr: WidgetTemplateAttr,
|
||||||
|
pub(super) mutable: Option<Mut>,
|
||||||
|
pub(super) name: Ident,
|
||||||
|
name_assigned_by_user: bool,
|
||||||
|
func: WidgetFunc,
|
||||||
|
args: Option<Args<Expr>>,
|
||||||
|
properties: Properties,
|
||||||
|
assign_wrapper: Option<Path>,
|
||||||
|
ref_token: Option<token::And>,
|
||||||
|
deref_token: Option<token::Star>,
|
||||||
|
returned_widget: Option<ReturnedWidget>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum WidgetAttr {
|
||||||
|
None,
|
||||||
|
Local,
|
||||||
|
LocalRef,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum WidgetTemplateAttr {
|
||||||
|
None,
|
||||||
|
Template,
|
||||||
|
TemplateChild,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ReturnedWidget {
|
||||||
|
name: Ident,
|
||||||
|
ty: Option<Path>,
|
||||||
|
properties: Properties,
|
||||||
|
is_optional: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ConditionalWidget {
|
||||||
|
doc_attr: Option<TokenStream2>,
|
||||||
|
transition: Option<Ident>,
|
||||||
|
assign_wrapper: Option<Path>,
|
||||||
|
name: Ident,
|
||||||
|
args: Option<Args<Expr>>,
|
||||||
|
branches: ConditionalBranches,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum ConditionalBranches {
|
||||||
|
If(Vec<IfBranch>),
|
||||||
|
Match((Match, Box<Expr>, Vec<MatchArm>)),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum IfCondition {
|
||||||
|
If(If, Expr),
|
||||||
|
ElseIf(Else, If, Expr),
|
||||||
|
Else(Else),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct IfBranch {
|
||||||
|
cond: IfCondition,
|
||||||
|
widget: Widget,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct MatchArm {
|
||||||
|
pattern: Pat,
|
||||||
|
guard: Option<(If, Box<Expr>)>,
|
||||||
|
arrow: FatArrow,
|
||||||
|
widget: Widget,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Attr {
|
||||||
|
Doc(TokenStream2),
|
||||||
|
Local(Ident),
|
||||||
|
LocalRef(Ident),
|
||||||
|
Root(Ident),
|
||||||
|
Iterate(Ident),
|
||||||
|
Watch(Ident, Option<Ident>),
|
||||||
|
Track(Ident, Option<Ident>, Option<Box<Expr>>),
|
||||||
|
BlockSignal(Ident, Vec<Ident>),
|
||||||
|
Name(Ident, Ident),
|
||||||
|
Transition(Ident, Ident),
|
||||||
|
Wrap(Ident, Path),
|
||||||
|
Chain(Ident, Box<Expr>),
|
||||||
|
Template(Ident),
|
||||||
|
TemplateChild(Ident),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Attrs {
|
||||||
|
inner: Vec<Attr>,
|
||||||
|
}
|
||||||
190
Relm4-0.6.2/relm4-macros/src/widgets/parse/assign_property.rs
Normal file
190
Relm4-0.6.2/relm4-macros/src/widgets/parse/assign_property.rs
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::{quote_spanned, ToTokens};
|
||||||
|
use syn::parse::ParseStream;
|
||||||
|
use syn::spanned::Spanned;
|
||||||
|
use syn::{Error, Expr, ExprCall, ExprField, Ident, Member, Result, Token};
|
||||||
|
|
||||||
|
use crate::args::Args;
|
||||||
|
use crate::widgets::parse_util::attr_twice_error;
|
||||||
|
use crate::widgets::{AssignProperty, AssignPropertyAttr, Attr, Attrs};
|
||||||
|
|
||||||
|
struct ProcessedAttrs {
|
||||||
|
watch: AssignPropertyAttr,
|
||||||
|
iterative: bool,
|
||||||
|
block_signals: Vec<Ident>,
|
||||||
|
chain: Option<Box<Expr>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AssignProperty {
|
||||||
|
pub(super) fn parse(
|
||||||
|
input: ParseStream<'_>,
|
||||||
|
attributes: Option<Attrs>,
|
||||||
|
args: Option<Args<Expr>>,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let optional_assign = input.parse::<Token![?]>().is_ok();
|
||||||
|
let colon: Token! [:] = input.parse()?;
|
||||||
|
let colon_span = colon.span();
|
||||||
|
|
||||||
|
let expr = match input.parse() {
|
||||||
|
Ok(expr) => expr,
|
||||||
|
Err(parse_err) => {
|
||||||
|
let mut err = Error::new(colon_span, "Did you confuse `=` with`:`?");
|
||||||
|
err.combine(parse_err);
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let ProcessedAttrs {
|
||||||
|
watch,
|
||||||
|
iterative,
|
||||||
|
block_signals,
|
||||||
|
chain,
|
||||||
|
} = Self::process_attributes(&expr, attributes)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
attr: watch,
|
||||||
|
expr,
|
||||||
|
args,
|
||||||
|
optional_assign,
|
||||||
|
iterative,
|
||||||
|
block_signals,
|
||||||
|
chain,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_attributes(assign_expr: &Expr, attrs: Option<Attrs>) -> Result<ProcessedAttrs> {
|
||||||
|
if let Some(attrs) = attrs {
|
||||||
|
let mut iterative = false;
|
||||||
|
let mut watch = AssignPropertyAttr::None;
|
||||||
|
let mut block_signals = Vec::with_capacity(0);
|
||||||
|
let mut chain = None;
|
||||||
|
|
||||||
|
for attr in attrs.inner {
|
||||||
|
let span = attr.span();
|
||||||
|
match attr {
|
||||||
|
Attr::Iterate(_) => {
|
||||||
|
if iterative {
|
||||||
|
return Err(attr_twice_error(span));
|
||||||
|
}
|
||||||
|
iterative = true;
|
||||||
|
}
|
||||||
|
Attr::Watch(_, skip_init) => {
|
||||||
|
if watch == AssignPropertyAttr::None {
|
||||||
|
watch = AssignPropertyAttr::Watch { skip_init }
|
||||||
|
} else {
|
||||||
|
return Err(attr_twice_error(span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Attr::Track(_, skip_init, expr) => {
|
||||||
|
if watch == AssignPropertyAttr::None {
|
||||||
|
watch = if let Some(expr) = expr {
|
||||||
|
AssignPropertyAttr::Track {
|
||||||
|
track_expr: expr.to_token_stream(),
|
||||||
|
skip_init,
|
||||||
|
paste_model: false,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
AssignPropertyAttr::Track {
|
||||||
|
track_expr: generate_tracker_from_expression(assign_expr)?,
|
||||||
|
skip_init,
|
||||||
|
paste_model: true,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return Err(attr_twice_error(span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Attr::BlockSignal(_, idents) => {
|
||||||
|
if block_signals.is_empty() {
|
||||||
|
block_signals = idents;
|
||||||
|
} else {
|
||||||
|
return Err(attr_twice_error(span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Attr::Chain(_, expr) => {
|
||||||
|
if chain.is_none() {
|
||||||
|
chain = Some(expr);
|
||||||
|
} else {
|
||||||
|
return Err(attr_twice_error(span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(Error::new(
|
||||||
|
attr.span(),
|
||||||
|
"Properties can only have `watch`, `track` or `iterative` as attribute.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(ProcessedAttrs {
|
||||||
|
watch,
|
||||||
|
iterative,
|
||||||
|
block_signals,
|
||||||
|
chain,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(ProcessedAttrs {
|
||||||
|
watch: AssignPropertyAttr::None,
|
||||||
|
iterative: false,
|
||||||
|
block_signals: Vec::with_capacity(0),
|
||||||
|
chain: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function for the tracker attribute.
|
||||||
|
fn expr_field_from_expr_call(call_expr: &ExprCall) -> Option<&ExprField> {
|
||||||
|
let first_expr = call_expr.args.iter().next()?;
|
||||||
|
if let Expr::Field(expr_field) = first_expr {
|
||||||
|
Some(expr_field)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_tracker_from_expression(expression: &Expr) -> Result<TokenStream2> {
|
||||||
|
let error_fn = move |span, msg: &str| {
|
||||||
|
let error_msg =
|
||||||
|
"Unable to generate tracker function. Please pass a condition as string value of the `track` attribute.\n\
|
||||||
|
Usage: #[track = \"TRACK_CONDITION\"]";
|
||||||
|
Err(Error::new(span, format!("{error_msg}\nHint: {msg}")))
|
||||||
|
};
|
||||||
|
|
||||||
|
let unref_expr: &Expr = if let Expr::Reference(expr_ref) = expression {
|
||||||
|
&expr_ref.expr
|
||||||
|
} else {
|
||||||
|
expression
|
||||||
|
};
|
||||||
|
|
||||||
|
let expr_field_opt = match unref_expr {
|
||||||
|
Expr::Call(call_expr) => expr_field_from_expr_call(call_expr),
|
||||||
|
Expr::MethodCall(expr_method_call) => {
|
||||||
|
if let Expr::Field(ref expr_field) = *expr_method_call.receiver {
|
||||||
|
Some(expr_field)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::Field(field_expr) => Some(field_expr),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let expr_field = if let Some(expr_field) = expr_field_opt {
|
||||||
|
expr_field
|
||||||
|
} else {
|
||||||
|
return error_fn(
|
||||||
|
unref_expr.span(),
|
||||||
|
"Couldn't find find a call or method expression.",
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let ident = if let Member::Named(ident) = &expr_field.member {
|
||||||
|
ident.clone()
|
||||||
|
} else {
|
||||||
|
return error_fn(expr_field.member.span(), "Expected a named member");
|
||||||
|
};
|
||||||
|
|
||||||
|
let bool_stream = quote_spanned! { expr_field.span() => .changed(Self::#ident()) };
|
||||||
|
Ok(bool_stream)
|
||||||
|
}
|
||||||
214
Relm4-0.6.2/relm4-macros/src/widgets/parse/attributes.rs
Normal file
214
Relm4-0.6.2/relm4-macros/src/widgets/parse/attributes.rs
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
use quote::ToTokens;
|
||||||
|
use syn::parse::{Parse, ParseStream};
|
||||||
|
use syn::punctuated::Punctuated;
|
||||||
|
use syn::spanned::Spanned;
|
||||||
|
use syn::{bracketed, parenthesized, token, Error, Expr, Ident, Lit, LitStr, Path, Result, Token};
|
||||||
|
|
||||||
|
use crate::widgets::{Attr, Attrs};
|
||||||
|
|
||||||
|
impl Parse for Attrs {
|
||||||
|
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||||
|
let mut attrs = Vec::new();
|
||||||
|
|
||||||
|
while input.peek(Token![#]) {
|
||||||
|
let _sharp: Token![#] = input.parse()?;
|
||||||
|
let attr_tokens;
|
||||||
|
bracketed!(attr_tokens in input);
|
||||||
|
let path: Path = attr_tokens.parse()?;
|
||||||
|
|
||||||
|
// Name attribute
|
||||||
|
attrs.push(if attr_tokens.is_empty() {
|
||||||
|
if let Some(ident) = path.get_ident() {
|
||||||
|
if ident == "local" {
|
||||||
|
Attr::Local(ident.clone())
|
||||||
|
} else if ident == "local_ref" {
|
||||||
|
Attr::LocalRef(ident.clone())
|
||||||
|
} else if ident == "root" {
|
||||||
|
Attr::Root(ident.clone())
|
||||||
|
} else if ident == "watch" {
|
||||||
|
Attr::Watch(ident.clone(), None)
|
||||||
|
} else if ident == "track" {
|
||||||
|
Attr::Track(ident.clone(), None, None)
|
||||||
|
} else if ident == "iterate" {
|
||||||
|
Attr::Iterate(ident.clone())
|
||||||
|
} else if ident == "template" {
|
||||||
|
Attr::Template(ident.clone())
|
||||||
|
} else if ident == "template_child" {
|
||||||
|
Attr::TemplateChild(ident.clone())
|
||||||
|
} else {
|
||||||
|
return Err(unexpected_attr_name(ident));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(Error::new(path.span(), "Expected identifier."));
|
||||||
|
}
|
||||||
|
|
||||||
|
// List attribute: `#[name(item1, item2)]
|
||||||
|
} else if attr_tokens.peek(token::Paren) {
|
||||||
|
let paren_input;
|
||||||
|
parenthesized!(paren_input in attr_tokens);
|
||||||
|
let nested: Punctuated<Expr, token::Comma> =
|
||||||
|
Punctuated::parse_terminated(&paren_input)?;
|
||||||
|
|
||||||
|
if let Some(ident) = path.get_ident() {
|
||||||
|
if ident == "block_signal" {
|
||||||
|
let mut signal_idents = Vec::with_capacity(nested.len());
|
||||||
|
for expr in nested {
|
||||||
|
let ident = expect_ident_from_expr(&expr)?;
|
||||||
|
signal_idents.push(ident);
|
||||||
|
}
|
||||||
|
Attr::BlockSignal(ident.clone(), signal_idents)
|
||||||
|
} else if ident == "watch" {
|
||||||
|
let expr = expect_one_nested_expr(&nested)?;
|
||||||
|
if let Some(skip_init) = expr_to_skip_init_ident(expr) {
|
||||||
|
Attr::Watch(ident.clone(), Some(skip_init))
|
||||||
|
} else {
|
||||||
|
return Err(Error::new(nested.span(), "Expected `skip_init`."));
|
||||||
|
}
|
||||||
|
} else if ident == "track" {
|
||||||
|
let (skip_init, expr) = parse_track(&nested)?;
|
||||||
|
Attr::Track(ident.clone(), skip_init, expr.map(Box::new))
|
||||||
|
} else if ident == "transition" {
|
||||||
|
let expr = expect_one_nested_expr(&nested)?;
|
||||||
|
let ident = expect_ident_from_expr(expr)?;
|
||||||
|
Attr::Transition(ident.clone(), ident)
|
||||||
|
} else if ident == "name" {
|
||||||
|
let expr = expect_one_nested_expr(&nested)?;
|
||||||
|
let ident = expect_ident_from_expr(expr)?;
|
||||||
|
Attr::Name(ident.clone(), ident)
|
||||||
|
} else if ident == "wrap" {
|
||||||
|
let expr = expect_one_nested_expr(&nested)?;
|
||||||
|
let path = expect_path_from_expr(expr)?;
|
||||||
|
Attr::Wrap(ident.clone(), path)
|
||||||
|
} else if ident == "chain" {
|
||||||
|
let expr = expect_one_nested_expr(&nested)?;
|
||||||
|
Attr::Chain(ident.clone(), Box::new(expr.clone()))
|
||||||
|
} else {
|
||||||
|
return Err(unexpected_attr_name(ident));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(Error::new(path.span(), "Expected identifier."));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value attribute: `#[name = literal)]
|
||||||
|
} else if attr_tokens.peek(Token![=]) {
|
||||||
|
let _eq: Token![=] = attr_tokens.parse()?;
|
||||||
|
let lit = attr_tokens.parse()?;
|
||||||
|
|
||||||
|
if let Some(ident) = path.get_ident() {
|
||||||
|
if ident == "track" {
|
||||||
|
let string = expect_string_lit(&lit)?;
|
||||||
|
Attr::Track(ident.clone(), None, Some(string.parse()?))
|
||||||
|
} else if ident == "doc" {
|
||||||
|
Attr::Doc(lit.into_token_stream())
|
||||||
|
} else if ident == "transition" {
|
||||||
|
let string = expect_string_lit(&lit)?;
|
||||||
|
Attr::Transition(ident.clone(), string.parse()?)
|
||||||
|
} else if ident == "name" {
|
||||||
|
let string = expect_string_lit(&lit)?;
|
||||||
|
Attr::Name(ident.clone(), string.parse()?)
|
||||||
|
} else {
|
||||||
|
return Err(unexpected_attr_name(ident));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(Error::new(path.span(), "Expected identifier."));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(Error::new(attr_tokens.span(), "Expected `]`, `(` or `=`."));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Attrs { inner: attrs })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unexpected_attr_name(ident: &Ident) -> Error {
|
||||||
|
Error::new(
|
||||||
|
ident.span(),
|
||||||
|
format!("Unexpected attribute name `{ident}`."),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_string_lit(lit: &Lit) -> Result<&LitStr> {
|
||||||
|
if let Lit::Str(string) = lit {
|
||||||
|
Ok(string)
|
||||||
|
} else {
|
||||||
|
Err(Error::new(
|
||||||
|
lit.span(),
|
||||||
|
"Expected string literal. Try this: `\"value\"`.",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_path_from_expr(expr: &Expr) -> Result<Path> {
|
||||||
|
if let Expr::Path(path) = expr {
|
||||||
|
Ok(path.path.clone())
|
||||||
|
} else {
|
||||||
|
Err(Error::new(
|
||||||
|
expr.span(),
|
||||||
|
format!("Expected path `{}`.", expr.to_token_stream()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_ident_from_expr(expr: &Expr) -> Result<Ident> {
|
||||||
|
if let Expr::Path(path) = expr {
|
||||||
|
expect_ident_from_path(&path.path)
|
||||||
|
} else {
|
||||||
|
Err(Error::new(
|
||||||
|
expr.span(),
|
||||||
|
format!("Expected identifier `{}`.", expr.to_token_stream()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_ident_from_path(path: &Path) -> Result<Ident> {
|
||||||
|
if let Some(ident) = path.get_ident() {
|
||||||
|
Ok(ident.clone())
|
||||||
|
} else {
|
||||||
|
Err(Error::new(path.span(), "Expected identifier."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_one_nested_expr(nested: &Punctuated<Expr, token::Comma>) -> Result<&Expr> {
|
||||||
|
if nested.len() == 1 {
|
||||||
|
Ok(nested.first().unwrap())
|
||||||
|
} else {
|
||||||
|
Err(Error::new(nested.span(), "Expected only one expression."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_track(nested: &Punctuated<Expr, token::Comma>) -> Result<(Option<Ident>, Option<Expr>)> {
|
||||||
|
let len = nested.len();
|
||||||
|
if len == 1 {
|
||||||
|
if let Some(skip_ident) = expr_to_skip_init_ident(&nested[0]) {
|
||||||
|
Ok((Some(skip_ident), None))
|
||||||
|
} else {
|
||||||
|
Ok((None, Some(nested[0].clone())))
|
||||||
|
}
|
||||||
|
} else if len == 2 {
|
||||||
|
if let Some(skip_ident) = expr_to_skip_init_ident(&nested[0]) {
|
||||||
|
Ok((Some(skip_ident), Some(nested.last().unwrap().clone())))
|
||||||
|
} else {
|
||||||
|
Err(Error::new(
|
||||||
|
nested.span(),
|
||||||
|
"Expected `skip_init` and an expression.",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(Error::new(
|
||||||
|
nested.span(),
|
||||||
|
"Expected exactly one or two expressions.",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expr_to_skip_init_ident(expr: &Expr) -> Option<Ident> {
|
||||||
|
if let Expr::Path(path) = &expr {
|
||||||
|
if let Some(ident) = path.path.get_ident() {
|
||||||
|
if ident == "skip_init" {
|
||||||
|
return Some(ident.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
use syn::parse::ParseStream;
|
||||||
|
use syn::{Expr, Token};
|
||||||
|
|
||||||
|
use crate::widgets::{parse_util, ConditionalBranches, IfBranch, MatchArm, ParseError};
|
||||||
|
|
||||||
|
impl ConditionalBranches {
|
||||||
|
pub(super) fn parse_if(input: ParseStream<'_>) -> Result<Self, ParseError> {
|
||||||
|
let mut if_branches = Vec::new();
|
||||||
|
let mut index = 0_usize;
|
||||||
|
while input.peek(Token![if]) || input.peek(Token![else]) {
|
||||||
|
if_branches.push(IfBranch::parse(input, index)?);
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
Ok(Self::If(if_branches))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn parse_match(input: ParseStream<'_>) -> Result<Self, ParseError> {
|
||||||
|
let match_token = input.parse()?;
|
||||||
|
let expr = Box::new(Expr::parse_without_eager_brace(input)?);
|
||||||
|
|
||||||
|
let braced = parse_util::braces(input)?;
|
||||||
|
|
||||||
|
let mut match_arms = Vec::new();
|
||||||
|
let mut index = 0_usize;
|
||||||
|
while !braced.is_empty() {
|
||||||
|
match_arms.push(MatchArm::parse(&braced, index)?);
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
Ok(Self::Match((match_token, expr, match_arms)))
|
||||||
|
}
|
||||||
|
}
|
||||||
134
Relm4-0.6.2/relm4-macros/src/widgets/parse/conditional_widget.rs
Normal file
134
Relm4-0.6.2/relm4-macros/src/widgets/parse/conditional_widget.rs
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use syn::parse::ParseStream;
|
||||||
|
use syn::{Error, Expr, Ident, Path, Token};
|
||||||
|
|
||||||
|
use crate::args::Args;
|
||||||
|
use crate::widgets::parse_util::{self, attr_twice_error};
|
||||||
|
use crate::widgets::{Attr, Attrs, ConditionalBranches, ConditionalWidget, ParseError};
|
||||||
|
|
||||||
|
type ConditionalAttrs = (
|
||||||
|
Option<Ident>,
|
||||||
|
Option<Ident>,
|
||||||
|
Option<TokenStream2>,
|
||||||
|
Option<Path>,
|
||||||
|
);
|
||||||
|
|
||||||
|
impl ConditionalWidget {
|
||||||
|
pub(super) fn parse(
|
||||||
|
input: ParseStream<'_>,
|
||||||
|
attrs: Option<Attrs>,
|
||||||
|
args: Option<Args<Expr>>,
|
||||||
|
) -> Result<Self, ParseError> {
|
||||||
|
let name = if input.peek2(Token![=]) && !input.peek2(Token![==]) && !input.peek(Token![!]) {
|
||||||
|
let name = input.parse()?;
|
||||||
|
let _assign: Token![=] = input.parse()?;
|
||||||
|
Some(name)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
Self::parse_with_name(input, name, attrs, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn parse_with_name(
|
||||||
|
input: ParseStream<'_>,
|
||||||
|
name: Option<Ident>,
|
||||||
|
attrs: Option<Attrs>,
|
||||||
|
args: Option<Args<Expr>>,
|
||||||
|
) -> Result<Self, ParseError> {
|
||||||
|
let (transition, attr_name, doc_attr, assign_wrapper) = Self::process_attrs(attrs)?;
|
||||||
|
|
||||||
|
if attr_name.is_some() {
|
||||||
|
if let Some(name) = &name {
|
||||||
|
return Err(Error::new(
|
||||||
|
name.span(),
|
||||||
|
"Name defined as attribute and redefined here.",
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = if let Some(name) = name {
|
||||||
|
name
|
||||||
|
} else if let Some(name) = attr_name {
|
||||||
|
name
|
||||||
|
} else {
|
||||||
|
parse_util::idents_to_snake_case(
|
||||||
|
[Ident::new("conditional_widget", input.span())].iter(),
|
||||||
|
input.span(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
if input.peek(Token![if]) {
|
||||||
|
let branches = ConditionalBranches::parse_if(input)?;
|
||||||
|
Ok(Self {
|
||||||
|
doc_attr,
|
||||||
|
transition,
|
||||||
|
assign_wrapper,
|
||||||
|
name,
|
||||||
|
args,
|
||||||
|
branches,
|
||||||
|
})
|
||||||
|
} else if input.peek(Token![match]) {
|
||||||
|
let branches = ConditionalBranches::parse_match(input)?;
|
||||||
|
Ok(Self {
|
||||||
|
doc_attr,
|
||||||
|
transition,
|
||||||
|
assign_wrapper,
|
||||||
|
name,
|
||||||
|
args,
|
||||||
|
branches,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(input.error("Expected `if` or `match`").into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_attrs(attrs: Option<Attrs>) -> Result<ConditionalAttrs, ParseError> {
|
||||||
|
let mut transition = None;
|
||||||
|
let mut name = None;
|
||||||
|
let mut doc_attr: Option<TokenStream2> = None;
|
||||||
|
let mut assign_wrapper = None;
|
||||||
|
|
||||||
|
if let Some(attrs) = attrs {
|
||||||
|
for attr in attrs.inner {
|
||||||
|
let span = attr.span();
|
||||||
|
match attr {
|
||||||
|
Attr::Transition(_, transition_value) => {
|
||||||
|
if transition.is_none() {
|
||||||
|
transition = Some(transition_value);
|
||||||
|
} else {
|
||||||
|
return Err(attr_twice_error(span).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Attr::Name(_, name_value) => {
|
||||||
|
if name.is_none() {
|
||||||
|
name = Some(name_value);
|
||||||
|
} else {
|
||||||
|
return Err(attr_twice_error(span).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Attr::Wrap(_, path) => {
|
||||||
|
if assign_wrapper.is_some() {
|
||||||
|
return Err(attr_twice_error(span).into());
|
||||||
|
}
|
||||||
|
assign_wrapper = Some(path.clone());
|
||||||
|
}
|
||||||
|
Attr::Doc(tokens) => {
|
||||||
|
if let Some(doc_tokens) = &mut doc_attr {
|
||||||
|
doc_tokens.extend(tokens);
|
||||||
|
} else {
|
||||||
|
doc_attr = Some(tokens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(Error::new(
|
||||||
|
attr.span(),
|
||||||
|
"Conditional widgets can only have docs and `name` or `transition` as attribute.",
|
||||||
|
).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok((transition, name, doc_attr, assign_wrapper))
|
||||||
|
}
|
||||||
|
}
|
||||||
43
Relm4-0.6.2/relm4-macros/src/widgets/parse/if_branch.rs
Normal file
43
Relm4-0.6.2/relm4-macros/src/widgets/parse/if_branch.rs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
use proc_macro2::Span as Span2;
|
||||||
|
use syn::parse::ParseStream;
|
||||||
|
use syn::punctuated::Punctuated;
|
||||||
|
use syn::token::{And, Paren};
|
||||||
|
use syn::{Expr, ExprCall, ExprLit, ExprPath, Ident, Lit, LitStr};
|
||||||
|
|
||||||
|
use crate::args::Args;
|
||||||
|
use crate::widgets::{parse_util, IfBranch, ParseError, Widget};
|
||||||
|
|
||||||
|
impl IfBranch {
|
||||||
|
pub(super) fn parse(input: ParseStream<'_>, index: usize) -> Result<Self, ParseError> {
|
||||||
|
let cond = input.parse()?;
|
||||||
|
|
||||||
|
let braced = parse_util::braces(input)?;
|
||||||
|
|
||||||
|
let attributes = braced.parse().ok();
|
||||||
|
let args = args_from_index(index, input.span());
|
||||||
|
let mut widget = Widget::parse(&braced, attributes, Some(args))?;
|
||||||
|
widget.ref_token = Some(And {
|
||||||
|
spans: [Span2::call_site()],
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(Self { cond, widget })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn args_from_index(index: usize, span: Span2) -> Args<Expr> {
|
||||||
|
Args {
|
||||||
|
inner: vec![Expr::Call(ExprCall {
|
||||||
|
attrs: Vec::new(),
|
||||||
|
func: Box::new(Expr::Path(ExprPath {
|
||||||
|
attrs: Vec::new(),
|
||||||
|
qself: None,
|
||||||
|
path: Ident::new("Some", span).into(),
|
||||||
|
})),
|
||||||
|
paren_token: Paren(span),
|
||||||
|
args: Punctuated::from_iter(vec![Expr::Lit(ExprLit {
|
||||||
|
attrs: Vec::new(),
|
||||||
|
lit: Lit::Str(LitStr::new(&format!("{index}"), span)),
|
||||||
|
})]),
|
||||||
|
})],
|
||||||
|
}
|
||||||
|
}
|
||||||
21
Relm4-0.6.2/relm4-macros/src/widgets/parse/if_condition.rs
Normal file
21
Relm4-0.6.2/relm4-macros/src/widgets/parse/if_condition.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
use syn::parse::{Parse, ParseStream};
|
||||||
|
use syn::{Result, Token};
|
||||||
|
|
||||||
|
use crate::widgets::IfCondition;
|
||||||
|
|
||||||
|
impl Parse for IfCondition {
|
||||||
|
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||||
|
if input.peek(Token![if]) {
|
||||||
|
Ok(Self::If(input.parse()?, input.parse()?))
|
||||||
|
} else if input.peek(Token![else]) {
|
||||||
|
let else_token = input.parse()?;
|
||||||
|
if input.peek(Token![if]) {
|
||||||
|
Ok(Self::ElseIf(else_token, input.parse()?, input.parse()?))
|
||||||
|
} else {
|
||||||
|
Ok(Self::Else(else_token))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(input.error("Expected `if`, `if else` or `else`"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
45
Relm4-0.6.2/relm4-macros/src/widgets/parse/match_arm.rs
Normal file
45
Relm4-0.6.2/relm4-macros/src/widgets/parse/match_arm.rs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
use syn::parse::ParseStream;
|
||||||
|
use syn::token::And;
|
||||||
|
use syn::{token, Token};
|
||||||
|
|
||||||
|
use crate::widgets::parse::if_branch::args_from_index;
|
||||||
|
use crate::widgets::{parse_util, MatchArm, ParseError, Widget};
|
||||||
|
|
||||||
|
impl MatchArm {
|
||||||
|
pub(super) fn parse(input: ParseStream<'_>, index: usize) -> Result<Self, ParseError> {
|
||||||
|
if input.peek(Token![,]) {
|
||||||
|
let _comma: Token![,] = input.parse()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pattern = syn::Pat::parse_multi_with_leading_vert(input)?;
|
||||||
|
let guard = if input.peek(token::FatArrow) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some((input.parse()?, input.parse()?))
|
||||||
|
};
|
||||||
|
|
||||||
|
let arrow = input.parse()?;
|
||||||
|
|
||||||
|
let braced;
|
||||||
|
let inner_tokens = if input.peek(token::Brace) {
|
||||||
|
braced = parse_util::braces(input)?;
|
||||||
|
&braced
|
||||||
|
} else {
|
||||||
|
input
|
||||||
|
};
|
||||||
|
|
||||||
|
let attributes = inner_tokens.parse().ok();
|
||||||
|
let args = args_from_index(index, input.span());
|
||||||
|
|
||||||
|
let ref_span = input.span();
|
||||||
|
let mut widget = Widget::parse(inner_tokens, attributes, Some(args))?;
|
||||||
|
widget.ref_token = Some(And { spans: [ref_span] });
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
pattern,
|
||||||
|
guard,
|
||||||
|
arrow,
|
||||||
|
widget,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
17
Relm4-0.6.2/relm4-macros/src/widgets/parse/mod.rs
Normal file
17
Relm4-0.6.2/relm4-macros/src/widgets/parse/mod.rs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
mod assign_property;
|
||||||
|
mod attributes;
|
||||||
|
mod conditional_branches;
|
||||||
|
mod conditional_widget;
|
||||||
|
mod if_branch;
|
||||||
|
mod if_condition;
|
||||||
|
mod match_arm;
|
||||||
|
mod properties;
|
||||||
|
mod property;
|
||||||
|
mod property_name;
|
||||||
|
mod returned_widget;
|
||||||
|
mod signal_handler;
|
||||||
|
mod top_level_widget;
|
||||||
|
mod view_widgets;
|
||||||
|
mod widget;
|
||||||
|
mod widget_func;
|
||||||
|
mod widget_func_method;
|
||||||
143
Relm4-0.6.2/relm4-macros/src/widgets/parse/properties.rs
Normal file
143
Relm4-0.6.2/relm4-macros/src/widgets/parse/properties.rs
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
use proc_macro2::{Literal, Punct};
|
||||||
|
use syn::ext::IdentExt;
|
||||||
|
use syn::parse::discouraged::Speculative;
|
||||||
|
use syn::parse::ParseStream;
|
||||||
|
use syn::punctuated::{Pair, Punctuated};
|
||||||
|
use syn::token::{And, At, Caret, Colon, Dot, Gt, Lt, Or, Question, Slash, Tilde, Underscore};
|
||||||
|
use syn::{braced, bracketed, parenthesized, token, Ident, Lifetime, Token};
|
||||||
|
|
||||||
|
use crate::widgets::{parse_util, ParseError, Properties, Property, PropertyName, PropertyType};
|
||||||
|
|
||||||
|
impl Properties {
|
||||||
|
pub(super) fn parse(input: ParseStream<'_>) -> Self {
|
||||||
|
let mut props: Punctuated<Property, Token![,]> = Punctuated::new();
|
||||||
|
loop {
|
||||||
|
if input.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let parse_input = input.fork();
|
||||||
|
let (prop, contains_error) = Property::parse(&parse_input);
|
||||||
|
props.push(prop);
|
||||||
|
|
||||||
|
// Everything worked, advance input
|
||||||
|
if !contains_error {
|
||||||
|
input.advance_to(&parse_input);
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(prop) = parse_comma_error(input) {
|
||||||
|
// If there's already an error, ignore the additional comma error
|
||||||
|
if contains_error {
|
||||||
|
// Skip to next token to start with "fresh" and hopefully correct syntax.
|
||||||
|
while !parse_next_token(input).unwrap() {
|
||||||
|
let next_input = input.fork();
|
||||||
|
let (prop, contains_error) = Property::parse(&next_input);
|
||||||
|
if !contains_error {
|
||||||
|
// Point with correct syntax was found!
|
||||||
|
props.push(prop);
|
||||||
|
input.advance_to(&next_input);
|
||||||
|
|
||||||
|
// Now we should definitely have a comma
|
||||||
|
if let Err(prop) = parse_comma_error(input) {
|
||||||
|
props.push(prop);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
props.push(prop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let properties = props.into_pairs().map(Pair::into_value).collect();
|
||||||
|
Properties { properties }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_comma_error(input: ParseStream<'_>) -> Result<(), Property> {
|
||||||
|
let lookahead = input.lookahead1();
|
||||||
|
if lookahead.peek(Token![,]) {
|
||||||
|
input.parse::<Token![,]>().unwrap();
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
let err = lookahead.error();
|
||||||
|
Err(Property {
|
||||||
|
name: PropertyName::Ident(parse_util::string_to_snake_case("comma_error")),
|
||||||
|
ty: PropertyType::ParseError(ParseError::Generic(err.to_compile_error())),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! parse_type {
|
||||||
|
($input:ident, $ty:ty) => {
|
||||||
|
let _: $ty = $input.parse()?;
|
||||||
|
return Ok(false);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skip_inner_tokens(input: ParseStream<'_>) -> Result<(), syn::Error> {
|
||||||
|
while !input.is_empty() {
|
||||||
|
parse_next_token(input)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_next_token(input: ParseStream<'_>) -> Result<bool, syn::Error> {
|
||||||
|
let inner_tokens;
|
||||||
|
if input.is_empty() {
|
||||||
|
Ok(true)
|
||||||
|
} else if input.peek(Token![,]) {
|
||||||
|
let _comma: Token![,] = input.parse()?;
|
||||||
|
Ok(true)
|
||||||
|
} else if input.peek(token::Paren) {
|
||||||
|
parenthesized!(inner_tokens in input);
|
||||||
|
skip_inner_tokens(&inner_tokens)?;
|
||||||
|
Ok(false)
|
||||||
|
} else if input.peek(token::Bracket) {
|
||||||
|
bracketed!(inner_tokens in input);
|
||||||
|
skip_inner_tokens(&inner_tokens)?;
|
||||||
|
Ok(false)
|
||||||
|
} else if input.peek(token::Brace) {
|
||||||
|
braced!(inner_tokens in input);
|
||||||
|
skip_inner_tokens(&inner_tokens)?;
|
||||||
|
Ok(false)
|
||||||
|
} else if Ident::parse_any(input).is_ok() {
|
||||||
|
Ok(false)
|
||||||
|
} else if input.peek(And) {
|
||||||
|
parse_type!(input, And);
|
||||||
|
} else if input.peek(At) {
|
||||||
|
parse_type!(input, At);
|
||||||
|
} else if input.peek(Colon) {
|
||||||
|
parse_type!(input, Colon);
|
||||||
|
} else if input.peek(Slash) {
|
||||||
|
parse_type!(input, Slash);
|
||||||
|
} else if input.peek(syn::token::Eq) {
|
||||||
|
parse_type!(input, syn::token::Eq);
|
||||||
|
} else if input.peek(Gt) {
|
||||||
|
parse_type!(input, Gt);
|
||||||
|
} else if input.peek(Lt) {
|
||||||
|
parse_type!(input, Lt);
|
||||||
|
} else if input.peek(Or) {
|
||||||
|
parse_type!(input, Or);
|
||||||
|
} else if input.peek(Tilde) {
|
||||||
|
parse_type!(input, Tilde);
|
||||||
|
} else if input.peek(Caret) {
|
||||||
|
parse_type!(input, Caret);
|
||||||
|
} else if input.peek(Underscore) {
|
||||||
|
parse_type!(input, Underscore);
|
||||||
|
} else if input.peek(Question) {
|
||||||
|
parse_type!(input, Question);
|
||||||
|
} else if input.peek(Dot) {
|
||||||
|
parse_type!(input, Dot);
|
||||||
|
} else if input.peek(Lifetime) {
|
||||||
|
parse_type!(input, Lifetime);
|
||||||
|
} else if input.parse::<Punct>().is_ok() || input.parse::<Literal>().is_ok() {
|
||||||
|
Ok(false)
|
||||||
|
} else {
|
||||||
|
unreachable!("Every possible token should be covered. Please report this error at Relm4! \nContext: '''{input}''' \n");
|
||||||
|
}
|
||||||
|
}
|
||||||
142
Relm4-0.6.2/relm4-macros/src/widgets/parse/property.rs
Normal file
142
Relm4-0.6.2/relm4-macros/src/widgets/parse/property.rs
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
use syn::parse::ParseStream;
|
||||||
|
use syn::spanned::Spanned;
|
||||||
|
use syn::{token, Error, Ident, Token};
|
||||||
|
|
||||||
|
use crate::widgets::{
|
||||||
|
parse_util, AssignProperty, Attrs, ConditionalWidget, ParseError, Property, PropertyName,
|
||||||
|
PropertyType, SignalHandler, Widget, WidgetFunc,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Property {
|
||||||
|
pub(super) fn parse(input: ParseStream<'_>) -> (Self, bool) {
|
||||||
|
match Self::parse_failing(input) {
|
||||||
|
Ok(prop) => (prop, false),
|
||||||
|
Err(err) => (
|
||||||
|
Self {
|
||||||
|
name: PropertyName::Ident(parse_util::string_to_snake_case("invalid_property")),
|
||||||
|
ty: PropertyType::ParseError(err),
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_failing(input: ParseStream<'_>) -> Result<Self, ParseError> {
|
||||||
|
// Handle `#[attrs]`
|
||||||
|
let mut attributes: Option<Attrs> = if input.peek(Token![#]) {
|
||||||
|
Some(input.parse()?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// parse `if something { WIDGET } else { WIDGET}` or a similar match expression.
|
||||||
|
if input.peek(Token![if]) || input.peek(Token![match]) {
|
||||||
|
return Ok(Property {
|
||||||
|
name: PropertyName::RelmContainerExtAssign(input.span()),
|
||||||
|
ty: PropertyType::ConditionalWidget(ConditionalWidget::parse_with_name(
|
||||||
|
input,
|
||||||
|
None,
|
||||||
|
attributes.take(),
|
||||||
|
None,
|
||||||
|
)?),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse path, ident or function
|
||||||
|
let func = WidgetFunc::parse(input)?;
|
||||||
|
|
||||||
|
// `gtk::Box { ... }`, `data.init_widget() -> gtk::Button { ... }` or `gtk::Box,`
|
||||||
|
if input.peek(token::Brace) || input.peek(Token![->]) || input.peek(Token![,]) {
|
||||||
|
let span = func.span();
|
||||||
|
let ty =
|
||||||
|
PropertyType::Widget(Widget::parse_for_container_ext(input, func, attributes)?);
|
||||||
|
|
||||||
|
Ok(Property {
|
||||||
|
name: PropertyName::RelmContainerExtAssign(span),
|
||||||
|
ty,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let name = func.into_property_name()?;
|
||||||
|
|
||||||
|
// check for property[a, b, c]: ...
|
||||||
|
let mut args = if input.peek(token::Bracket) {
|
||||||
|
let paren_input = parse_util::brackets(input)?;
|
||||||
|
Some(paren_input.parse()?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// look for event handlers: signal[cloned_data, ...] => move |a, ...| { ... }
|
||||||
|
let ty = if input.peek(Token! [=>]) {
|
||||||
|
let _arrow: Token![=>] = input.parse()?;
|
||||||
|
PropertyType::SignalHandler(SignalHandler::parse_with_args(input, args.take())?)
|
||||||
|
}
|
||||||
|
// look for widgets
|
||||||
|
else if (input.peek(Token![=])
|
||||||
|
|| input.peek3(Token![=])
|
||||||
|
|| (input.peek(Token![:]) && input.peek2(Token![mut]) && input.peek3(Ident)))
|
||||||
|
// Don't interpret `property: value == other,` as a widget
|
||||||
|
&& !input.peek3(Token![==])
|
||||||
|
{
|
||||||
|
let is_conditional = if input.peek(Token![=]) {
|
||||||
|
let _token: Token![=] = input.parse()?;
|
||||||
|
input.peek(Token![if]) || input.peek(Token![match])
|
||||||
|
} else {
|
||||||
|
let _colon: Token![:] = input.parse()?;
|
||||||
|
input.peek3(Token![if]) || input.peek3(Token![match])
|
||||||
|
};
|
||||||
|
// match expression
|
||||||
|
if is_conditional {
|
||||||
|
PropertyType::ConditionalWidget(ConditionalWidget::parse(
|
||||||
|
input,
|
||||||
|
attributes.take(),
|
||||||
|
args.take(),
|
||||||
|
)?)
|
||||||
|
} else {
|
||||||
|
PropertyType::Widget(Widget::parse(input, attributes.take(), args.take())?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// look for properties or optional properties (?)
|
||||||
|
else if input.peek(Token! [:]) || input.peek(Token! [?]) {
|
||||||
|
// look for ? at beginning for optional assign
|
||||||
|
PropertyType::Assign(AssignProperty::parse(
|
||||||
|
input,
|
||||||
|
attributes.take(),
|
||||||
|
args.take(),
|
||||||
|
)?)
|
||||||
|
} else {
|
||||||
|
return Err(input.error("Unexpected syntax.").into());
|
||||||
|
};
|
||||||
|
|
||||||
|
// Attributes must have been set to `None` by `take()`
|
||||||
|
if let Some(attrs) = attributes {
|
||||||
|
if let Some(first_attr) = attrs.inner.first() {
|
||||||
|
return Err(Error::new(
|
||||||
|
first_attr.span(),
|
||||||
|
"No attributes allowed in the following expression.",
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arguments must have been set to `None` by `take()`
|
||||||
|
if let Some(args) = args {
|
||||||
|
if let Some(first_arg) = args.inner.first() {
|
||||||
|
return Err(Error::new(
|
||||||
|
first_arg.span(),
|
||||||
|
"No arguments allowed in this expression.",
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !input.is_empty() && !input.peek(Token![,]) {
|
||||||
|
Err(input
|
||||||
|
.error("expected `,`. Did you confuse `=` with`:`?")
|
||||||
|
.into())
|
||||||
|
} else {
|
||||||
|
Ok(Property { name, ty })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
Relm4-0.6.2/relm4-macros/src/widgets/parse/property_name.rs
Normal file
14
Relm4-0.6.2/relm4-macros/src/widgets/parse/property_name.rs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
use syn::parse::{Parse, ParseStream};
|
||||||
|
use syn::{Result, Token};
|
||||||
|
|
||||||
|
use crate::widgets::PropertyName;
|
||||||
|
|
||||||
|
impl Parse for PropertyName {
|
||||||
|
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||||
|
Ok(if input.peek(Token![::]) || input.peek2(Token! [::]) {
|
||||||
|
PropertyName::Path(input.parse()?)
|
||||||
|
} else {
|
||||||
|
PropertyName::Ident(input.parse()?)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
use syn::parse::{Parse, ParseStream};
|
||||||
|
use syn::spanned::Spanned;
|
||||||
|
use syn::{braced, Ident, Result, Token};
|
||||||
|
|
||||||
|
use crate::widgets::{parse_util, Properties, ReturnedWidget};
|
||||||
|
|
||||||
|
impl Parse for ReturnedWidget {
|
||||||
|
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||||
|
let mut is_optional = false;
|
||||||
|
|
||||||
|
let (name, ty) = if input.peek(Ident) {
|
||||||
|
let name = input.parse()?;
|
||||||
|
|
||||||
|
let _colon: Token![:] = input.parse()?;
|
||||||
|
let ty = input.parse()?;
|
||||||
|
|
||||||
|
if input.peek(Token![?]) {
|
||||||
|
let _mark: Token![?] = input.parse()?;
|
||||||
|
is_optional = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
(Some(name), Some(ty))
|
||||||
|
} else {
|
||||||
|
if input.peek(Token![?]) {
|
||||||
|
let _mark: Token![?] = input.parse()?;
|
||||||
|
is_optional = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
(None, None)
|
||||||
|
};
|
||||||
|
|
||||||
|
let name = name.unwrap_or_else(|| {
|
||||||
|
parse_util::idents_to_snake_case(
|
||||||
|
[Ident::new("_returned_widget", input.span())].iter(),
|
||||||
|
ty.span(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let inner;
|
||||||
|
let _token = braced!(inner in input);
|
||||||
|
let properties = Properties::parse(&inner);
|
||||||
|
|
||||||
|
Ok(ReturnedWidget {
|
||||||
|
name,
|
||||||
|
ty,
|
||||||
|
properties,
|
||||||
|
is_optional,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
37
Relm4-0.6.2/relm4-macros/src/widgets/parse/signal_handler.rs
Normal file
37
Relm4-0.6.2/relm4-macros/src/widgets/parse/signal_handler.rs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
use syn::parse::ParseStream;
|
||||||
|
use syn::{Expr, Result, Token};
|
||||||
|
|
||||||
|
use crate::widgets::{Args, ClosureSignalHandler, SignalHandler, SignalHandlerVariant};
|
||||||
|
|
||||||
|
impl SignalHandler {
|
||||||
|
pub(super) fn parse_with_args(
|
||||||
|
input: ParseStream<'_>,
|
||||||
|
args: Option<Args<Expr>>,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let inner = if args.is_some() || input.peek(Token![move]) || input.peek(Token![|]) {
|
||||||
|
ClosureSignalHandler::parse_with_args(input, args).map(SignalHandlerVariant::Closure)
|
||||||
|
} else {
|
||||||
|
input.parse().map(SignalHandlerVariant::Expr)
|
||||||
|
}?;
|
||||||
|
|
||||||
|
let handler_id = if input.peek(Token![@]) {
|
||||||
|
let _arrow: Token![@] = input.parse()?;
|
||||||
|
input.parse()?
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self { inner, handler_id })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClosureSignalHandler {
|
||||||
|
pub(super) fn parse_with_args(
|
||||||
|
input: ParseStream<'_>,
|
||||||
|
args: Option<Args<Expr>>,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let closure = input.parse()?;
|
||||||
|
|
||||||
|
Ok(Self { closure, args })
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
use syn::parse::ParseStream;
|
||||||
|
|
||||||
|
use crate::util;
|
||||||
|
use crate::widgets::{
|
||||||
|
parse_util, Attr, Attrs, Properties, Property, PropertyName, PropertyType, TopLevelWidget,
|
||||||
|
Widget, WidgetAttr, WidgetFunc, WidgetTemplateAttr,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl TopLevelWidget {
|
||||||
|
pub(super) fn parse(input: ParseStream<'_>) -> Self {
|
||||||
|
let attributes: Option<Attrs> = input.parse().ok();
|
||||||
|
|
||||||
|
// Look for #[root] attribute and remove it from the list if it exists
|
||||||
|
let (attributes, root_attr) = if let Some(prev_attributes) = attributes {
|
||||||
|
let mut attributes = Attrs {
|
||||||
|
inner: Vec::with_capacity(prev_attributes.inner.len()),
|
||||||
|
};
|
||||||
|
let mut root_attr = None;
|
||||||
|
for attr in prev_attributes.inner {
|
||||||
|
match attr {
|
||||||
|
Attr::Root(ident) => {
|
||||||
|
// Save root attribute and don't push it to the new list
|
||||||
|
root_attr = Some(ident);
|
||||||
|
}
|
||||||
|
_ => attributes.inner.push(attr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Some(attributes), root_attr)
|
||||||
|
} else {
|
||||||
|
(None, None)
|
||||||
|
};
|
||||||
|
|
||||||
|
let inner = match Widget::parse(input, attributes, None) {
|
||||||
|
Ok(inner) => inner,
|
||||||
|
Err(err) => Widget {
|
||||||
|
doc_attr: None,
|
||||||
|
attr: WidgetAttr::None,
|
||||||
|
template_attr: WidgetTemplateAttr::None,
|
||||||
|
mutable: None,
|
||||||
|
name: parse_util::string_to_snake_case("incorrect_top_level_widget"),
|
||||||
|
name_assigned_by_user: false,
|
||||||
|
func: WidgetFunc {
|
||||||
|
path: util::strings_to_path(&["gtk", "Box"]),
|
||||||
|
args: None,
|
||||||
|
method_chain: None,
|
||||||
|
ty: None,
|
||||||
|
},
|
||||||
|
args: None,
|
||||||
|
properties: Properties {
|
||||||
|
properties: vec![Property {
|
||||||
|
name: PropertyName::Ident(parse_util::string_to_snake_case(
|
||||||
|
"invalid_property",
|
||||||
|
)),
|
||||||
|
ty: PropertyType::ParseError(err),
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
assign_wrapper: None,
|
||||||
|
ref_token: None,
|
||||||
|
deref_token: None,
|
||||||
|
returned_widget: None,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Self { root_attr, inner }
|
||||||
|
}
|
||||||
|
}
|
||||||
39
Relm4-0.6.2/relm4-macros/src/widgets/parse/view_widgets.rs
Normal file
39
Relm4-0.6.2/relm4-macros/src/widgets/parse/view_widgets.rs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
use syn::parse::{Parse, ParseStream, Result};
|
||||||
|
use syn::{Error, Ident, Token};
|
||||||
|
|
||||||
|
use crate::widgets::{TopLevelWidget, ViewWidgets};
|
||||||
|
|
||||||
|
impl Parse for ViewWidgets {
|
||||||
|
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||||
|
let span = input.span();
|
||||||
|
|
||||||
|
let first_widget = TopLevelWidget::parse(input);
|
||||||
|
let mut root_exists = first_widget.root_attr.is_some();
|
||||||
|
let mut top_level_widgets = vec![first_widget];
|
||||||
|
|
||||||
|
// Parse colon between widgets and look for more
|
||||||
|
while input.parse::<Token![,]>().is_ok() && !input.is_empty() {
|
||||||
|
let widget = TopLevelWidget::parse(input);
|
||||||
|
if let Some(root_attr) = &widget.root_attr {
|
||||||
|
if root_exists {
|
||||||
|
return Err(Error::new(root_attr.span(), "You cannot have two roots."));
|
||||||
|
}
|
||||||
|
root_exists = true;
|
||||||
|
}
|
||||||
|
top_level_widgets.push(widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !root_exists && top_level_widgets.len() == 1 {
|
||||||
|
top_level_widgets[0].root_attr = Some(Ident::new("root", input.span()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.is_empty() {
|
||||||
|
Ok(Self {
|
||||||
|
span,
|
||||||
|
top_level_widgets,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(input.error("Unexpected end of input. Maybe a missing comma `,`?"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
285
Relm4-0.6.2/relm4-macros/src/widgets/parse/widget.rs
Normal file
285
Relm4-0.6.2/relm4-macros/src/widgets/parse/widget.rs
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use syn::parse::ParseStream;
|
||||||
|
use syn::spanned::Spanned;
|
||||||
|
use syn::token::{And, Star};
|
||||||
|
use syn::{Error, Expr, Ident, Path, Token};
|
||||||
|
|
||||||
|
use crate::args::Args;
|
||||||
|
use crate::widgets::parse_util::{self, attr_twice_error};
|
||||||
|
use crate::widgets::{
|
||||||
|
Attr, Attrs, ParseError, Properties, PropertyType, Widget, WidgetAttr, WidgetFunc,
|
||||||
|
WidgetTemplateAttr,
|
||||||
|
};
|
||||||
|
|
||||||
|
type WidgetFuncInfo = (Option<And>, Option<Star>, WidgetFunc, Properties);
|
||||||
|
|
||||||
|
type AttributeInfo = (
|
||||||
|
WidgetAttr,
|
||||||
|
Option<TokenStream2>,
|
||||||
|
Option<Ident>,
|
||||||
|
Option<Path>,
|
||||||
|
WidgetTemplateAttr,
|
||||||
|
);
|
||||||
|
|
||||||
|
impl Widget {
|
||||||
|
pub(super) fn parse(
|
||||||
|
input: ParseStream<'_>,
|
||||||
|
attributes: Option<Attrs>,
|
||||||
|
args: Option<Args<Expr>>,
|
||||||
|
) -> Result<Self, ParseError> {
|
||||||
|
let (attr, doc_attr, new_name, assign_wrapper, template_attr) =
|
||||||
|
Self::process_attributes(attributes)?;
|
||||||
|
// Check if first token is `mut`
|
||||||
|
let mutable = input.parse().ok();
|
||||||
|
|
||||||
|
// Look for name = Widget syntax
|
||||||
|
let name_opt: Option<Ident> = if input.peek2(Token![=]) {
|
||||||
|
if attr.is_local_attr() || template_attr == WidgetTemplateAttr::TemplateChild {
|
||||||
|
return Err(input.error("When using the `local`, `local_ref` or `template_child` attributes you cannot rename the existing local variable.").into());
|
||||||
|
}
|
||||||
|
let name = input.parse()?;
|
||||||
|
let _token: Token![=] = input.parse()?;
|
||||||
|
Some(name)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let (ref_token, deref_token, func, properties) = Self::parse_widget_func(input)?;
|
||||||
|
|
||||||
|
// Make sure that the name is only defined one.
|
||||||
|
let mut name_set = name_opt.is_some();
|
||||||
|
if new_name.is_some() {
|
||||||
|
if name_set {
|
||||||
|
return Err(Error::new(name_opt.unwrap().span(), "Widget name is specified more than once (attribute, assignment or local attribute).").into());
|
||||||
|
}
|
||||||
|
name_set = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if name_set && (attr.is_local_attr() || template_attr == WidgetTemplateAttr::TemplateChild)
|
||||||
|
{
|
||||||
|
return Err(Error::new(input.span(), "Widget name is specified more than once (attribute, assignment or local attribute).").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a name if no name was given.
|
||||||
|
let (name, name_assigned_by_user) = if let Some(name) = name_opt.or(new_name) {
|
||||||
|
(name, true)
|
||||||
|
} else if attr.is_local_attr() || template_attr == WidgetTemplateAttr::TemplateChild {
|
||||||
|
(Self::local_attr_name(&func)?, true)
|
||||||
|
} else {
|
||||||
|
(func.snake_case_name(), false)
|
||||||
|
};
|
||||||
|
|
||||||
|
let returned_widget = if input.peek(Token![->]) {
|
||||||
|
let _arrow: Token![->] = input.parse()?;
|
||||||
|
Some(input.parse()?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Self::check_props(&properties, &template_attr)?;
|
||||||
|
|
||||||
|
Ok(Widget {
|
||||||
|
doc_attr,
|
||||||
|
attr,
|
||||||
|
template_attr,
|
||||||
|
mutable,
|
||||||
|
name,
|
||||||
|
name_assigned_by_user,
|
||||||
|
func,
|
||||||
|
args,
|
||||||
|
properties,
|
||||||
|
assign_wrapper,
|
||||||
|
ref_token,
|
||||||
|
deref_token,
|
||||||
|
returned_widget,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn parse_for_container_ext(
|
||||||
|
input: ParseStream<'_>,
|
||||||
|
func: WidgetFunc,
|
||||||
|
attributes: Option<Attrs>,
|
||||||
|
) -> Result<Self, ParseError> {
|
||||||
|
let (attr, doc_attr, new_name, assign_wrapper, template_attr) =
|
||||||
|
Self::process_attributes(attributes)?;
|
||||||
|
|
||||||
|
if let Some(wrapper) = assign_wrapper {
|
||||||
|
return Err(Error::new(
|
||||||
|
wrapper.span(),
|
||||||
|
"Can't use wrapper types in container assignment.",
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let properties = if input.peek(Token![,]) {
|
||||||
|
Properties::default()
|
||||||
|
} else {
|
||||||
|
let inner = parse_util::braces(input)?;
|
||||||
|
Properties::parse(&inner)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make sure that the name is only defined one.
|
||||||
|
if attr.is_local_attr() || template_attr == WidgetTemplateAttr::TemplateChild {
|
||||||
|
if let Some(name) = &new_name {
|
||||||
|
return Err(Error::new(name.span(), "Widget name is specified more than once (attribute, assignment or local attribute).").into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a name
|
||||||
|
let (name, name_assigned_by_user) = if let Some(name) = new_name {
|
||||||
|
(name, true)
|
||||||
|
} else if attr.is_local_attr() || template_attr == WidgetTemplateAttr::TemplateChild {
|
||||||
|
(Self::local_attr_name(&func)?, true)
|
||||||
|
} else {
|
||||||
|
(func.snake_case_name(), false)
|
||||||
|
};
|
||||||
|
|
||||||
|
let ref_token = Some(And::default());
|
||||||
|
|
||||||
|
Self::check_props(&properties, &template_attr)?;
|
||||||
|
|
||||||
|
Ok(Widget {
|
||||||
|
doc_attr,
|
||||||
|
attr,
|
||||||
|
template_attr,
|
||||||
|
mutable: None,
|
||||||
|
name,
|
||||||
|
name_assigned_by_user,
|
||||||
|
func,
|
||||||
|
args: None,
|
||||||
|
properties,
|
||||||
|
assign_wrapper,
|
||||||
|
ref_token,
|
||||||
|
deref_token: None,
|
||||||
|
returned_widget: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_props(
|
||||||
|
props: &Properties,
|
||||||
|
template_attr: &WidgetTemplateAttr,
|
||||||
|
) -> Result<(), ParseError> {
|
||||||
|
// Make sure template_child is only used in a valid context.
|
||||||
|
if template_attr != &WidgetTemplateAttr::Template {
|
||||||
|
for prop in &props.properties {
|
||||||
|
if let PropertyType::Widget(widget) = &prop.ty {
|
||||||
|
if widget.template_attr == WidgetTemplateAttr::TemplateChild {
|
||||||
|
return Err(ParseError::Generic(
|
||||||
|
syn::Error::new(
|
||||||
|
widget.name.span(),
|
||||||
|
"You can't use a template child if the parent is not a template.",
|
||||||
|
)
|
||||||
|
.to_compile_error(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_attributes(attrs: Option<Attrs>) -> Result<AttributeInfo, ParseError> {
|
||||||
|
if let Some(attrs) = attrs {
|
||||||
|
let mut widget_attr = WidgetAttr::None;
|
||||||
|
let mut doc_attr: Option<TokenStream2> = None;
|
||||||
|
let mut name = None;
|
||||||
|
let mut assign_wrapper = None;
|
||||||
|
let mut template_attr = WidgetTemplateAttr::None;
|
||||||
|
|
||||||
|
for attr in attrs.inner {
|
||||||
|
let span = attr.span();
|
||||||
|
match attr {
|
||||||
|
Attr::Local(_) => {
|
||||||
|
if widget_attr == WidgetAttr::None {
|
||||||
|
widget_attr = WidgetAttr::Local;
|
||||||
|
} else {
|
||||||
|
return Err(attr_twice_error(span).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Attr::LocalRef(_) => {
|
||||||
|
if widget_attr == WidgetAttr::None {
|
||||||
|
widget_attr = WidgetAttr::LocalRef;
|
||||||
|
} else {
|
||||||
|
return Err(attr_twice_error(span).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Attr::Doc(tokens) => {
|
||||||
|
if let Some(doc_tokens) = &mut doc_attr {
|
||||||
|
doc_tokens.extend(tokens);
|
||||||
|
} else {
|
||||||
|
doc_attr = Some(tokens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Attr::Name(_, name_value) => {
|
||||||
|
if name.is_some() {
|
||||||
|
return Err(attr_twice_error(span).into());
|
||||||
|
}
|
||||||
|
name = Some(name_value);
|
||||||
|
}
|
||||||
|
Attr::Wrap(_, path) => {
|
||||||
|
if assign_wrapper.is_some() {
|
||||||
|
return Err(attr_twice_error(span).into());
|
||||||
|
}
|
||||||
|
assign_wrapper = Some(path.clone());
|
||||||
|
}
|
||||||
|
Attr::Template(_) => {
|
||||||
|
if template_attr != WidgetTemplateAttr::None {
|
||||||
|
return Err(attr_twice_error(span).into());
|
||||||
|
}
|
||||||
|
template_attr = WidgetTemplateAttr::Template;
|
||||||
|
}
|
||||||
|
Attr::TemplateChild(_) => {
|
||||||
|
if template_attr != WidgetTemplateAttr::None {
|
||||||
|
return Err(attr_twice_error(span).into());
|
||||||
|
}
|
||||||
|
template_attr = WidgetTemplateAttr::TemplateChild;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(Error::new(
|
||||||
|
attr.span(),
|
||||||
|
"Widgets can only have docs and `local`, `local_ref`, `wrap`, `name`, `template`, `template_child` or `root` as attribute.",
|
||||||
|
).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((widget_attr, doc_attr, name, assign_wrapper, template_attr))
|
||||||
|
} else {
|
||||||
|
Ok((WidgetAttr::None, None, None, None, WidgetTemplateAttr::None))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure that the widget function is just a single identifier of the
|
||||||
|
// local variable if a local attribute was set.
|
||||||
|
fn local_attr_name(func: &WidgetFunc) -> Result<Ident, ParseError> {
|
||||||
|
if let Some(name) = func.path.get_ident() {
|
||||||
|
Ok(name.clone())
|
||||||
|
} else {
|
||||||
|
Err(Error::new(
|
||||||
|
func.path.span(),
|
||||||
|
"Expected identifier due to the `local`, `local_ref` or `template_child` attribute.",
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse information related to the widget function.
|
||||||
|
fn parse_widget_func(input: ParseStream<'_>) -> Result<WidgetFuncInfo, ParseError> {
|
||||||
|
// Look for &
|
||||||
|
let ref_token = input.parse().ok();
|
||||||
|
|
||||||
|
// Look for *
|
||||||
|
let deref_token = input.parse().ok();
|
||||||
|
|
||||||
|
let func = WidgetFunc::parse(input)?;
|
||||||
|
|
||||||
|
let properties = if input.peek(Token![,]) {
|
||||||
|
Properties::default()
|
||||||
|
} else {
|
||||||
|
let inner = parse_util::braces(input)?;
|
||||||
|
Properties::parse(&inner)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((ref_token, deref_token, func, properties))
|
||||||
|
}
|
||||||
|
}
|
||||||
65
Relm4-0.6.2/relm4-macros/src/widgets/parse/widget_func.rs
Normal file
65
Relm4-0.6.2/relm4-macros/src/widgets/parse/widget_func.rs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
use syn::parse::ParseStream;
|
||||||
|
use syn::punctuated::Punctuated;
|
||||||
|
use syn::spanned::Spanned;
|
||||||
|
use syn::{token, Ident, Path, Token};
|
||||||
|
|
||||||
|
use crate::widgets::{parse_util, ParseError, WidgetFunc};
|
||||||
|
|
||||||
|
impl WidgetFunc {
|
||||||
|
pub(super) fn parse_with_path(input: ParseStream<'_>, path: &Path) -> Result<Self, ParseError> {
|
||||||
|
match Self::parse_with_path_internal(input, path) {
|
||||||
|
Ok(func) => Ok(func),
|
||||||
|
Err(err) => Err(err.add_path(path)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_with_path_internal(input: ParseStream<'_>, path: &Path) -> Result<Self, ParseError> {
|
||||||
|
if input.peek(Ident) {
|
||||||
|
return Err(ParseError::Generic(
|
||||||
|
syn::Error::new(
|
||||||
|
path.span()
|
||||||
|
.join(input.span())
|
||||||
|
.unwrap_or_else(|| input.span()),
|
||||||
|
"A path must not be followed by an identifier",
|
||||||
|
)
|
||||||
|
.into_compile_error(),
|
||||||
|
)
|
||||||
|
.add_path(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
let args = if input.peek(token::Paren) {
|
||||||
|
let paren_input = parse_util::parens(input)?;
|
||||||
|
Some(paren_input.call(Punctuated::parse_terminated)?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let method_chain = if input.peek(token::Dot) {
|
||||||
|
let _dot: token::Dot = input.parse()?;
|
||||||
|
Some(Punctuated::parse_separated_nonempty(input)?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let ty = if input.peek(Token! [->]) {
|
||||||
|
let _token: Token! [->] = input.parse()?;
|
||||||
|
Some(input.parse()?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(WidgetFunc {
|
||||||
|
path: path.clone(),
|
||||||
|
args,
|
||||||
|
method_chain,
|
||||||
|
ty,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WidgetFunc {
|
||||||
|
pub(super) fn parse(input: ParseStream<'_>) -> Result<Self, ParseError> {
|
||||||
|
let path = &input.parse()?;
|
||||||
|
Self::parse_with_path(input, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
use syn::parse::{Parse, ParseStream};
|
||||||
|
use syn::punctuated::Punctuated;
|
||||||
|
use syn::{parenthesized, token, Result};
|
||||||
|
|
||||||
|
use crate::widgets::WidgetFuncMethod;
|
||||||
|
|
||||||
|
impl Parse for WidgetFuncMethod {
|
||||||
|
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||||
|
let ident = input.parse()?;
|
||||||
|
let turbofish = input.parse().ok();
|
||||||
|
let args = if input.peek(token::Paren) {
|
||||||
|
let inner_input;
|
||||||
|
parenthesized!(inner_input in input);
|
||||||
|
Some(Punctuated::parse_terminated(&inner_input)?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
ident,
|
||||||
|
turbofish,
|
||||||
|
args,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
162
Relm4-0.6.2/relm4-macros/src/widgets/parse_util.rs
Normal file
162
Relm4-0.6.2/relm4-macros/src/widgets/parse_util.rs
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
use std::sync::atomic::{AtomicU16, Ordering};
|
||||||
|
|
||||||
|
use proc_macro2::Span as Span2;
|
||||||
|
use syn::parse::ParseBuffer;
|
||||||
|
use syn::spanned::Spanned;
|
||||||
|
use syn::{braced, bracketed, parenthesized, Error, Ident, Path};
|
||||||
|
|
||||||
|
use super::{ParseError, PropertyName};
|
||||||
|
use crate::widgets::{AssignPropertyAttr, WidgetAttr, WidgetFunc};
|
||||||
|
|
||||||
|
pub(super) fn attr_twice_error(span: Span2) -> Error {
|
||||||
|
Error::new(span, "Cannot use the same attribute twice.")
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Error> for ParseError {
|
||||||
|
fn from(error: Error) -> Self {
|
||||||
|
Self::Generic(error.to_compile_error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParseError {
|
||||||
|
pub(super) fn add_path(self, path: &Path) -> Self {
|
||||||
|
if let ParseError::Generic(tokens) = self {
|
||||||
|
if let Some(ident) = path.get_ident() {
|
||||||
|
ParseError::Ident((ident.clone(), tokens))
|
||||||
|
} else {
|
||||||
|
ParseError::Path((path.clone(), tokens))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WidgetFunc {
|
||||||
|
pub(super) fn into_property_name(self) -> Result<PropertyName, Error> {
|
||||||
|
if let Some(methods) = &self.method_chain {
|
||||||
|
Err(Error::new(
|
||||||
|
methods.span(),
|
||||||
|
"Can't use method calls in property assignments",
|
||||||
|
))
|
||||||
|
} else if let Some(args) = &self.args {
|
||||||
|
Err(Error::new(
|
||||||
|
args.span(),
|
||||||
|
"Can't use function arguments in property assignments",
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(if let Some(ident) = self.path.get_ident() {
|
||||||
|
PropertyName::Ident(ident.clone())
|
||||||
|
} else {
|
||||||
|
PropertyName::Path(self.path)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WidgetFunc {
|
||||||
|
pub(super) fn snake_case_name(&self) -> Ident {
|
||||||
|
idents_to_snake_case(
|
||||||
|
self.path.segments.iter().map(|seg| &seg.ident),
|
||||||
|
self.path.span(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WidgetAttr {
|
||||||
|
pub(super) fn is_local_attr(&self) -> bool {
|
||||||
|
matches!(self, Self::Local | Self::LocalRef)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AssignPropertyAttr {
|
||||||
|
pub(super) fn should_skip_init(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::None => false,
|
||||||
|
Self::Watch { skip_init } | Self::Track { skip_init, .. } => skip_init.is_some(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for AssignPropertyAttr {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
core::mem::discriminant(self) == core::mem::discriminant(other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn string_to_snake_case(string: &str) -> Ident {
|
||||||
|
idents_to_snake_case(
|
||||||
|
[Ident::new(string, Span2::call_site())].iter(),
|
||||||
|
Span2::call_site(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn idents_to_snake_case<'a, I: Iterator<Item = &'a Ident>>(
|
||||||
|
idents: I,
|
||||||
|
span: Span2,
|
||||||
|
) -> Ident {
|
||||||
|
static COUNTER: AtomicU16 = AtomicU16::new(0);
|
||||||
|
let val = COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||||
|
let index_str = val.to_string();
|
||||||
|
|
||||||
|
let segments: Vec<String> = idents
|
||||||
|
.map(|ident| ident.to_string().to_lowercase())
|
||||||
|
.collect();
|
||||||
|
let length: usize =
|
||||||
|
segments.iter().map(|seg| seg.len() + 1).sum::<usize>() + index_str.len() + 1;
|
||||||
|
let mut name: String = String::with_capacity(length);
|
||||||
|
|
||||||
|
for seg in &segments {
|
||||||
|
name.push('_');
|
||||||
|
name.push_str(seg);
|
||||||
|
}
|
||||||
|
name.push('_');
|
||||||
|
name.push_str(&index_str);
|
||||||
|
|
||||||
|
Ident::new(&name, span)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Weird hack to work around syn's awkward macros
|
||||||
|
/// that always return [`syn::Error`] and are worse
|
||||||
|
/// in every aspect compared to regular Rust code.
|
||||||
|
///
|
||||||
|
/// Sadly, the regular Rust API won't be made public,
|
||||||
|
/// see [#1190](https://github.com/dtolnay/syn/issues/1190).
|
||||||
|
pub(super) fn parens<'a>(input: &'a ParseBuffer<'_>) -> Result<ParseBuffer<'a>, ParseError> {
|
||||||
|
let content = (move || {
|
||||||
|
let content;
|
||||||
|
parenthesized!(content in input);
|
||||||
|
Ok(content)
|
||||||
|
})();
|
||||||
|
Ok(content?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Weird hack to work around syn's awkward macros
|
||||||
|
/// that always return [`syn::Error`] and are worse
|
||||||
|
/// in every aspect compared to regular Rust code.
|
||||||
|
///
|
||||||
|
/// Sadly, the regular Rust API won't be made public,
|
||||||
|
/// see [#1190](https://github.com/dtolnay/syn/issues/1190).
|
||||||
|
pub(super) fn braces<'a>(input: &'a ParseBuffer<'_>) -> Result<ParseBuffer<'a>, ParseError> {
|
||||||
|
let content = (move || {
|
||||||
|
let content;
|
||||||
|
braced!(content in input);
|
||||||
|
Ok(content)
|
||||||
|
})();
|
||||||
|
Ok(content?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Weird hack to work around syn's awkward macros
|
||||||
|
/// that always return [`syn::Error`] and are worse
|
||||||
|
/// in every aspect compared to regular Rust code.
|
||||||
|
///
|
||||||
|
/// Sadly, the regular Rust API won't be made public,
|
||||||
|
/// see [#1190](https://github.com/dtolnay/syn/issues/1190).
|
||||||
|
pub(super) fn brackets<'a>(input: &'a ParseBuffer<'_>) -> Result<ParseBuffer<'a>, ParseError> {
|
||||||
|
let content = (move || {
|
||||||
|
let content;
|
||||||
|
bracketed!(content in input);
|
||||||
|
Ok(content)
|
||||||
|
})();
|
||||||
|
Ok(content?)
|
||||||
|
}
|
||||||
25
Relm4-0.6.2/relm4-macros/src/widgets/span/attr.rs
Normal file
25
Relm4-0.6.2/relm4-macros/src/widgets/span/attr.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
use proc_macro2::Span as Span2;
|
||||||
|
use syn::spanned::Spanned;
|
||||||
|
|
||||||
|
use crate::widgets::Attr;
|
||||||
|
|
||||||
|
impl Attr {
|
||||||
|
pub(crate) fn span(&self) -> Span2 {
|
||||||
|
match self {
|
||||||
|
Self::Doc(tokens) => tokens.span(),
|
||||||
|
Self::Local(ident)
|
||||||
|
| Self::LocalRef(ident)
|
||||||
|
| Self::Root(ident)
|
||||||
|
| Self::Iterate(ident)
|
||||||
|
| Self::Watch(ident, _)
|
||||||
|
| Self::Track(ident, _, _)
|
||||||
|
| Self::BlockSignal(ident, _)
|
||||||
|
| Self::Name(ident, _)
|
||||||
|
| Self::Transition(ident, _)
|
||||||
|
| Self::Chain(ident, _)
|
||||||
|
| Self::Template(ident)
|
||||||
|
| Self::TemplateChild(ident)
|
||||||
|
| Self::Wrap(ident, _) => ident.span(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
Relm4-0.6.2/relm4-macros/src/widgets/span/mod.rs
Normal file
6
Relm4-0.6.2/relm4-macros/src/widgets/span/mod.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
//! Span implementations
|
||||||
|
|
||||||
|
mod attr;
|
||||||
|
mod property_name;
|
||||||
|
mod widget_func;
|
||||||
|
mod widget_func_method;
|
||||||
14
Relm4-0.6.2/relm4-macros/src/widgets/span/property_name.rs
Normal file
14
Relm4-0.6.2/relm4-macros/src/widgets/span/property_name.rs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
use proc_macro2::Span as Span2;
|
||||||
|
use syn::spanned::Spanned;
|
||||||
|
|
||||||
|
use crate::widgets::PropertyName;
|
||||||
|
|
||||||
|
impl PropertyName {
|
||||||
|
pub(crate) fn span(&self) -> Span2 {
|
||||||
|
match self {
|
||||||
|
PropertyName::Ident(ident) => ident.span(),
|
||||||
|
PropertyName::Path(path) => path.span(),
|
||||||
|
PropertyName::RelmContainerExtAssign(span) => *span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
Relm4-0.6.2/relm4-macros/src/widgets/span/widget_func.rs
Normal file
10
Relm4-0.6.2/relm4-macros/src/widgets/span/widget_func.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
use proc_macro2::Span as Span2;
|
||||||
|
use syn::spanned::Spanned;
|
||||||
|
|
||||||
|
use crate::widgets::WidgetFunc;
|
||||||
|
|
||||||
|
impl WidgetFunc {
|
||||||
|
pub(crate) fn span(&self) -> Span2 {
|
||||||
|
self.path.span()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::{quote, ToTokens};
|
||||||
|
|
||||||
|
use crate::widgets::WidgetFuncMethod;
|
||||||
|
|
||||||
|
impl ToTokens for WidgetFuncMethod {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
|
let WidgetFuncMethod {
|
||||||
|
ident,
|
||||||
|
turbofish,
|
||||||
|
args,
|
||||||
|
} = &self;
|
||||||
|
tokens.extend(if let Some(args) = args {
|
||||||
|
quote! {
|
||||||
|
#ident #turbofish (#args)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
#ident #turbofish
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
40
Relm4-0.6.2/relm4-macros/tests/builder.rs
Normal file
40
Relm4-0.6.2/relm4-macros/tests/builder.rs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
use gtk::prelude::GtkWindowExt;
|
||||||
|
use relm4::{gtk, ComponentParts, ComponentSender, SimpleComponent};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct App;
|
||||||
|
|
||||||
|
#[relm4_macros::component]
|
||||||
|
impl SimpleComponent for App {
|
||||||
|
type Init = ();
|
||||||
|
type Input = ();
|
||||||
|
type Output = ();
|
||||||
|
type Widgets = AppWidgets;
|
||||||
|
|
||||||
|
view! {
|
||||||
|
gtk::Window {
|
||||||
|
set_title: Some("Simple app"),
|
||||||
|
set_default_size: (300, 100),
|
||||||
|
|
||||||
|
gtk::Box {
|
||||||
|
gtk::Builder::from_string("<Label id=\"label\"></Label>")
|
||||||
|
.object::<gtk::Label>("label")
|
||||||
|
.unwrap() -> gtk::Label {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
_init: Self::Init,
|
||||||
|
_root: &Self::Root,
|
||||||
|
_sender: ComponentSender<Self>,
|
||||||
|
) -> ComponentParts<Self> {
|
||||||
|
let model = Self;
|
||||||
|
|
||||||
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
ComponentParts { model, widgets }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _msg: Self::Input, _sender: ComponentSender<Self>) {}
|
||||||
|
}
|
||||||
34
Relm4-0.6.2/relm4-macros/tests/codegen_order.rs
Normal file
34
Relm4-0.6.2/relm4-macros/tests/codegen_order.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
use relm4::{ComponentParts, ComponentSender, SimpleComponent};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct App;
|
||||||
|
|
||||||
|
#[relm4_macros::component]
|
||||||
|
impl SimpleComponent for App {
|
||||||
|
type Init = ();
|
||||||
|
type Input = ();
|
||||||
|
type Output = ();
|
||||||
|
|
||||||
|
view! {
|
||||||
|
mut Vec::<Vec<u8>> {
|
||||||
|
push = mut Vec {
|
||||||
|
push: 0,
|
||||||
|
push: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
_init: Self::Init,
|
||||||
|
_root: &Self::Root,
|
||||||
|
_sender: ComponentSender<Self>,
|
||||||
|
) -> ComponentParts<Self> {
|
||||||
|
let model = Self;
|
||||||
|
|
||||||
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
ComponentParts { model, widgets }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _msg: Self::Input, _sender: ComponentSender<Self>) {}
|
||||||
|
}
|
||||||
46
Relm4-0.6.2/relm4-macros/tests/complex_func_path.rs
Normal file
46
Relm4-0.6.2/relm4-macros/tests/complex_func_path.rs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
use gtk::prelude::{GtkWindowExt, OrientableExt};
|
||||||
|
use relm4::{gtk, ComponentParts, ComponentSender, SimpleComponent};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct App;
|
||||||
|
|
||||||
|
trait TestType {
|
||||||
|
type Test;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestType for App {
|
||||||
|
type Test = gtk::Box;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[relm4_macros::component]
|
||||||
|
impl SimpleComponent for App {
|
||||||
|
type Init = ();
|
||||||
|
type Input = ();
|
||||||
|
type Output = ();
|
||||||
|
type Widgets = AppWidgets;
|
||||||
|
|
||||||
|
view! {
|
||||||
|
gtk::Window {
|
||||||
|
set_title: Some("Simple app"),
|
||||||
|
set_default_size: (300, 100),
|
||||||
|
|
||||||
|
gtk::Box::default() -> <App as TestType>::Test {
|
||||||
|
set_orientation: gtk::Orientation::Vertical,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
_: Self::Init,
|
||||||
|
root: &Self::Root,
|
||||||
|
_sender: ComponentSender<Self>,
|
||||||
|
) -> ComponentParts<Self> {
|
||||||
|
let model = Self;
|
||||||
|
|
||||||
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
ComponentParts { model, widgets }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _msg: (), _sender: ComponentSender<Self>) {}
|
||||||
|
}
|
||||||
28
Relm4-0.6.2/relm4-macros/tests/component_missing_widgets.rs
Normal file
28
Relm4-0.6.2/relm4-macros/tests/component_missing_widgets.rs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
use relm4::{gtk, ComponentParts, ComponentSender, SimpleComponent};
|
||||||
|
|
||||||
|
struct TestComponent;
|
||||||
|
|
||||||
|
#[relm4_macros::component]
|
||||||
|
impl SimpleComponent for TestComponent {
|
||||||
|
type Init = ();
|
||||||
|
type Input = ();
|
||||||
|
type Output = ();
|
||||||
|
|
||||||
|
view! {
|
||||||
|
gtk::Window {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
_: Self::Init,
|
||||||
|
root: &Self::Root,
|
||||||
|
_sender: ComponentSender<Self>,
|
||||||
|
) -> ComponentParts<Self> {
|
||||||
|
let model = Self;
|
||||||
|
|
||||||
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
ComponentParts { model, widgets }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
39
Relm4-0.6.2/relm4-macros/tests/iife.rs
Normal file
39
Relm4-0.6.2/relm4-macros/tests/iife.rs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
use relm4::{gtk, ComponentParts, ComponentSender, SimpleComponent};
|
||||||
|
|
||||||
|
struct App;
|
||||||
|
|
||||||
|
#[relm4_macros::component]
|
||||||
|
impl SimpleComponent for App {
|
||||||
|
type Init = ();
|
||||||
|
type Input = ();
|
||||||
|
type Output = ();
|
||||||
|
|
||||||
|
view! {
|
||||||
|
gtk::Window {
|
||||||
|
gtk::Label {
|
||||||
|
#[watch]
|
||||||
|
set_label: &format!("Counter: {counter}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pre_view() {
|
||||||
|
// Only works if pre_view isn't wrapped inside an IIFE
|
||||||
|
// because the local variable counter is used in the
|
||||||
|
// update_view method.
|
||||||
|
let counter = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
_counter: Self::Init,
|
||||||
|
root: &Self::Root,
|
||||||
|
_sender: ComponentSender<Self>,
|
||||||
|
) -> ComponentParts<Self> {
|
||||||
|
let model = Self;
|
||||||
|
|
||||||
|
let counter = 1;
|
||||||
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
ComponentParts { model, widgets }
|
||||||
|
}
|
||||||
|
}
|
||||||
44
Relm4-0.6.2/relm4-macros/tests/local_ref_container.rs
Normal file
44
Relm4-0.6.2/relm4-macros/tests/local_ref_container.rs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
use gtk::prelude::GtkWindowExt;
|
||||||
|
use relm4::{gtk, ComponentParts, ComponentSender, SimpleComponent};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct AppModel;
|
||||||
|
|
||||||
|
#[relm4_macros::component]
|
||||||
|
impl SimpleComponent for AppModel {
|
||||||
|
type Init = ();
|
||||||
|
type Input = ();
|
||||||
|
type Output = ();
|
||||||
|
|
||||||
|
view! {
|
||||||
|
gtk::Window {
|
||||||
|
set_title: Some("Simple app"),
|
||||||
|
set_default_width: 300,
|
||||||
|
set_default_height: 100,
|
||||||
|
|
||||||
|
#[local_ref]
|
||||||
|
my_box_ref -> gtk::Box {
|
||||||
|
gtk::Label {
|
||||||
|
set_label: "This should compile",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
_init: Self::Init,
|
||||||
|
_root: &Self::Root,
|
||||||
|
_sender: ComponentSender<Self>,
|
||||||
|
) -> ComponentParts<Self> {
|
||||||
|
let model = Self;
|
||||||
|
|
||||||
|
let my_box = gtk::Box::default();
|
||||||
|
let my_box_ref = &my_box;
|
||||||
|
|
||||||
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
ComponentParts { model, widgets }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _msg: (), _sender: ComponentSender<Self>) {}
|
||||||
|
}
|
||||||
56
Relm4-0.6.2/relm4-macros/tests/pub_widget_templates.rs
Normal file
56
Relm4-0.6.2/relm4-macros/tests/pub_widget_templates.rs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
use gtk::prelude::GtkWindowExt;
|
||||||
|
use relm4::{gtk, ComponentParts, ComponentSender, SimpleComponent};
|
||||||
|
|
||||||
|
pub mod templates {
|
||||||
|
use relm4::{gtk, WidgetTemplate};
|
||||||
|
|
||||||
|
#[relm4::widget_template(pub)]
|
||||||
|
impl WidgetTemplate for TestTemplate {
|
||||||
|
view! {
|
||||||
|
gtk::Box {
|
||||||
|
#[name(test_child)]
|
||||||
|
gtk::Label {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct AppModel;
|
||||||
|
|
||||||
|
#[relm4_macros::component]
|
||||||
|
impl SimpleComponent for AppModel {
|
||||||
|
type Init = ();
|
||||||
|
type Input = ();
|
||||||
|
type Output = ();
|
||||||
|
type Widgets = AppWidgets;
|
||||||
|
|
||||||
|
view! {
|
||||||
|
gtk::Window {
|
||||||
|
set_title: Some("Simple app"),
|
||||||
|
set_default_size: (300, 100),
|
||||||
|
|
||||||
|
#[template]
|
||||||
|
templates::TestTemplate {
|
||||||
|
#[template_child]
|
||||||
|
test_child -> gtk::Label {
|
||||||
|
set_label: "It works!",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
_: Self::Init,
|
||||||
|
root: &Self::Root,
|
||||||
|
_sender: ComponentSender<Self>,
|
||||||
|
) -> ComponentParts<Self> {
|
||||||
|
let model = Self;
|
||||||
|
|
||||||
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
ComponentParts { model, widgets }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _msg: (), _sender: ComponentSender<Self>) {}
|
||||||
|
}
|
||||||
39
Relm4-0.6.2/relm4-macros/tests/redundant_clone.rs
Normal file
39
Relm4-0.6.2/relm4-macros/tests/redundant_clone.rs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
use relm4::{
|
||||||
|
component::{AsyncComponent, AsyncComponentParts, AsyncComponentSender},
|
||||||
|
gtk,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct App;
|
||||||
|
|
||||||
|
#[relm4::component(pub async)]
|
||||||
|
impl AsyncComponent for App {
|
||||||
|
type Init = ();
|
||||||
|
type Input = ();
|
||||||
|
type Output = ();
|
||||||
|
type CommandOutput = ();
|
||||||
|
|
||||||
|
view! {
|
||||||
|
gtk::Window {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the component.
|
||||||
|
async fn init(
|
||||||
|
_init: Self::Init,
|
||||||
|
root: Self::Root,
|
||||||
|
_sender: AsyncComponentSender<Self>,
|
||||||
|
) -> AsyncComponentParts<Self> {
|
||||||
|
let model = Self;
|
||||||
|
|
||||||
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
AsyncComponentParts { model, widgets }
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update(
|
||||||
|
&mut self,
|
||||||
|
_msg: Self::Input,
|
||||||
|
_sender: AsyncComponentSender<Self>,
|
||||||
|
_root: &Self::Root,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
26
Relm4-0.6.2/relm4-macros/tests/returned_widgets.rs
Normal file
26
Relm4-0.6.2/relm4-macros/tests/returned_widgets.rs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
use relm4::gtk;
|
||||||
|
|
||||||
|
fn widget() -> gtk::Separator {
|
||||||
|
// Mimic component.widget() call
|
||||||
|
gtk::Separator::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let local_widget = widget();
|
||||||
|
relm4_macros::view! {
|
||||||
|
gtk::Stack {
|
||||||
|
add_child = >k::Separator::default() { }
|
||||||
|
-> { set_needs_attention: false },
|
||||||
|
add_child = >k::Separator::default() { }
|
||||||
|
-> page: gtk::StackPage { set_needs_attention: false },
|
||||||
|
add_child = &widget() {} -> {
|
||||||
|
set_needs_attention: false
|
||||||
|
},
|
||||||
|
add_child: &widget(),
|
||||||
|
#[local_ref]
|
||||||
|
add_child = &local_widget {} -> {
|
||||||
|
set_needs_attention: false
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
77
Relm4-0.6.2/relm4-macros/tests/simple.rs
Normal file
77
Relm4-0.6.2/relm4-macros/tests/simple.rs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
use gtk::prelude::{BoxExt, ButtonExt, GtkWindowExt, OrientableExt};
|
||||||
|
use relm4::{gtk, ComponentParts, ComponentSender, RelmWidgetExt, SimpleComponent};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct App {
|
||||||
|
counter: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum AppMsg {
|
||||||
|
Increment,
|
||||||
|
Decrement,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[relm4_macros::component]
|
||||||
|
impl SimpleComponent for App {
|
||||||
|
type Init = u8;
|
||||||
|
type Input = AppMsg;
|
||||||
|
type Output = ();
|
||||||
|
|
||||||
|
view! {
|
||||||
|
gtk::Window {
|
||||||
|
set_title: Some("Simple app"),
|
||||||
|
set_default_size: (300, 100),
|
||||||
|
|
||||||
|
gtk::Box {
|
||||||
|
set_orientation: gtk::Orientation::Vertical,
|
||||||
|
set_margin_all: 5,
|
||||||
|
set_spacing: 5,
|
||||||
|
|
||||||
|
append = >k::Button {
|
||||||
|
set_label: "Increment",
|
||||||
|
connect_clicked => AppMsg::Increment,
|
||||||
|
},
|
||||||
|
append = >k::Button {
|
||||||
|
set_label: "Decrement",
|
||||||
|
connect_clicked => AppMsg::Decrement,
|
||||||
|
},
|
||||||
|
append = >k::Label {
|
||||||
|
set_margin_all: 5,
|
||||||
|
#[watch]
|
||||||
|
set_label: &format!("Counter: {}", model.counter),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
counter: Self::Init,
|
||||||
|
root: &Self::Root,
|
||||||
|
sender: ComponentSender<Self>,
|
||||||
|
) -> ComponentParts<Self> {
|
||||||
|
let model = Self { counter };
|
||||||
|
|
||||||
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
ComponentParts { model, widgets }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, msg: AppMsg, _sender: ComponentSender<Self>) {
|
||||||
|
match msg {
|
||||||
|
AppMsg::Increment => {
|
||||||
|
self.counter = self.counter.wrapping_add(1);
|
||||||
|
}
|
||||||
|
AppMsg::Decrement => {
|
||||||
|
self.counter = self.counter.wrapping_sub(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_debug_impl<T: std::fmt::Debug>() {}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assert_widgets_impl_debug() {
|
||||||
|
assert_debug_impl::<AppWidgets>();
|
||||||
|
}
|
||||||
80
Relm4-0.6.2/relm4-macros/tests/skip_init.rs
Normal file
80
Relm4-0.6.2/relm4-macros/tests/skip_init.rs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
use gtk::prelude::{BoxExt, ButtonExt, GtkWindowExt, OrientableExt};
|
||||||
|
use relm4::{gtk, ComponentParts, ComponentSender, RelmWidgetExt, SimpleComponent};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct App {
|
||||||
|
counter: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum AppMsg {
|
||||||
|
Increment,
|
||||||
|
Decrement,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[relm4_macros::component]
|
||||||
|
impl SimpleComponent for App {
|
||||||
|
type Init = u8;
|
||||||
|
type Input = AppMsg;
|
||||||
|
type Output = ();
|
||||||
|
|
||||||
|
view! {
|
||||||
|
gtk::Window {
|
||||||
|
set_title: Some("Simple app"),
|
||||||
|
set_default_size: (300, 100),
|
||||||
|
|
||||||
|
gtk::Box {
|
||||||
|
set_orientation: gtk::Orientation::Vertical,
|
||||||
|
set_margin_all: 5,
|
||||||
|
set_spacing: 5,
|
||||||
|
|
||||||
|
append = >k::Button {
|
||||||
|
set_label: "Increment",
|
||||||
|
connect_clicked => AppMsg::Increment,
|
||||||
|
},
|
||||||
|
append = >k::Button {
|
||||||
|
set_label: "Decrement",
|
||||||
|
connect_clicked => AppMsg::Decrement,
|
||||||
|
},
|
||||||
|
append = >k::Label {
|
||||||
|
set_margin_all: 5,
|
||||||
|
#[watch(skip_init)]
|
||||||
|
set_label: &format!("Counter: {}{value}", model.counter),
|
||||||
|
#[track(skip_init, test)]
|
||||||
|
set_label: value,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pre_view() {
|
||||||
|
// These values are not available in init
|
||||||
|
// so we can make sure "skip_init" works
|
||||||
|
// by accessing the variables.
|
||||||
|
let test = true;
|
||||||
|
let value = "value";
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
counter: Self::Init,
|
||||||
|
root: &Self::Root,
|
||||||
|
sender: ComponentSender<Self>,
|
||||||
|
) -> ComponentParts<Self> {
|
||||||
|
let model = Self { counter };
|
||||||
|
|
||||||
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
ComponentParts { model, widgets }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, msg: AppMsg, _sender: ComponentSender<Self>) {
|
||||||
|
match msg {
|
||||||
|
AppMsg::Increment => {
|
||||||
|
self.counter = self.counter.wrapping_add(1);
|
||||||
|
}
|
||||||
|
AppMsg::Decrement => {
|
||||||
|
self.counter = self.counter.wrapping_sub(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
Relm4-0.6.2/relm4-macros/tests/ui/compile-fail/bad_impl.rs
Normal file
32
Relm4-0.6.2/relm4-macros/tests/ui/compile-fail/bad_impl.rs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
use relm4::{gtk, ComponentParts, ComponentSender, SimpleComponent};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct TestComponent;
|
||||||
|
|
||||||
|
#[relm4_macros::component]
|
||||||
|
impl SimpleComponent for TestComponent {
|
||||||
|
type Init = ();
|
||||||
|
type Input = ();
|
||||||
|
type Output = ();
|
||||||
|
|
||||||
|
view! {
|
||||||
|
gtk::Window {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
_init: Self::Init,
|
||||||
|
_root: &Self::Root,
|
||||||
|
_sender: ComponentSender<Self>,
|
||||||
|
) -> ComponentParts<Self> {
|
||||||
|
let model = Self::default();
|
||||||
|
|
||||||
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
ComponentParts { model, widgets }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Incorrect impl
|
||||||
|
fn
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
error: expected identifier, found `}`
|
||||||
|
--> tests/ui/compile-fail/bad_impl.rs:30:1
|
||||||
|
|
|
||||||
|
7 | impl SimpleComponent for TestComponent {
|
||||||
|
| - while parsing this item list starting here
|
||||||
|
...
|
||||||
|
30 | }
|
||||||
|
| ^
|
||||||
|
| |
|
||||||
|
| expected identifier
|
||||||
|
| the item list ends here
|
||||||
|
|
||||||
|
warning: unused import: `gtk`
|
||||||
|
--> tests/ui/compile-fail/bad_impl.rs:1:13
|
||||||
|
|
|
||||||
|
1 | use relm4::{gtk, ComponentParts, ComponentSender, SimpleComponent};
|
||||||
|
| ^^^
|
||||||
|
|
|
||||||
|
= note: `#[warn(unused_imports)]` on by default
|
||||||
|
|
||||||
|
error[E0046]: not all trait items implemented, missing: `Root`, `Widgets`, `init_root`
|
||||||
|
--> tests/ui/compile-fail/bad_impl.rs:7:1
|
||||||
|
|
|
||||||
|
7 | impl SimpleComponent for TestComponent {
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `Root`, `Widgets`, `init_root` in implementation
|
||||||
|
|
|
||||||
|
= help: implement the missing item: `type Root = /* Type */;`
|
||||||
|
= help: implement the missing item: `type Widgets = /* Type */;`
|
||||||
|
= help: implement the missing item: `fn init_root() -> <Self as SimpleComponent>::Root { todo!() }`
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
use relm4::SimpleComponent;
|
||||||
|
|
||||||
|
struct TestComponent;
|
||||||
|
|
||||||
|
#[relm4_macros::component]
|
||||||
|
impl SimpleComponent for TestComponent {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
error: expected `view!` macro invocation
|
||||||
|
--> tests/ui/compile-fail/component-empty.rs:6:1
|
||||||
|
|
|
||||||
|
6 | / impl SimpleComponent for TestComponent {
|
||||||
|
7 | |
|
||||||
|
8 | | }
|
||||||
|
| |_^
|
||||||
|
|
||||||
|
error[E0046]: not all trait items implemented, missing: `Input`, `Output`, `Init`, `Root`, `init_root`, `init`
|
||||||
|
--> tests/ui/compile-fail/component-empty.rs:6:1
|
||||||
|
|
|
||||||
|
6 | impl SimpleComponent for TestComponent {
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `Input`, `Output`, `Init`, `Root`, `init_root`, `init` in implementation
|
||||||
|
|
|
||||||
|
= help: implement the missing item: `type Input = /* Type */;`
|
||||||
|
= help: implement the missing item: `type Output = /* Type */;`
|
||||||
|
= help: implement the missing item: `type Init = /* Type */;`
|
||||||
|
= help: implement the missing item: `type Root = /* Type */;`
|
||||||
|
= help: implement the missing item: `fn init_root() -> <Self as SimpleComponent>::Root { todo!() }`
|
||||||
|
= help: implement the missing item: `fn init(_: <Self as SimpleComponent>::Init, _: &<Self as SimpleComponent>::Root, _: ComponentSender<Self>) -> ComponentParts<Self> { todo!() }`
|
||||||
34
Relm4-0.6.2/relm4-macros/tests/ui/compile-fail/dead_code.rs
Normal file
34
Relm4-0.6.2/relm4-macros/tests/ui/compile-fail/dead_code.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
use relm4::{gtk, ComponentParts, ComponentSender, SimpleComponent};
|
||||||
|
|
||||||
|
struct TestComponent;
|
||||||
|
|
||||||
|
#[relm4_macros::component]
|
||||||
|
impl SimpleComponent for TestComponent {
|
||||||
|
type Init = ();
|
||||||
|
type Input = ();
|
||||||
|
type Output = ();
|
||||||
|
|
||||||
|
view! {
|
||||||
|
gtk::Window {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(
|
||||||
|
_init: Self::Init,
|
||||||
|
_root: &Self::Root,
|
||||||
|
_sender: ComponentSender<Self>,
|
||||||
|
) -> ComponentParts<Self> {
|
||||||
|
let model = Self;
|
||||||
|
|
||||||
|
let widgets = view_output!();
|
||||||
|
|
||||||
|
// This would compile before 0.5.0-beta.3
|
||||||
|
// but shouldn't because the window isn't used
|
||||||
|
// and doesn't need to be part of the widgets
|
||||||
|
// struct.
|
||||||
|
let _window = &widgets._gtk_window_0;
|
||||||
|
|
||||||
|
ComponentParts { model, widgets }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user