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]]
|
||||
name = "libadwaita"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ab9c0843f9f23ff25634df2743690c3a1faffe0a190e60c490878517eb81abf"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"gdk-pixbuf",
|
||||
@ -1626,8 +1624,6 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||
[[package]]
|
||||
name = "relm4"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c16f3fad883034773b7f5af4d7e865532b8f3641e5a8bab2a34561a8d960d81"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"flume",
|
||||
@ -1654,8 +1650,6 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "relm4-macros"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9340e2553c0a184a80a0bfa1dcf73c47f3d48933aa6be90724b202f9fbd24735"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@ -7,8 +7,9 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
gtk = { version = "0.6.6", package = "gtk4", features = ["v4_8"] }
|
||||
adw = { version = "0.4.3", package = "libadwaita", features = ["v1_4"]}
|
||||
relm4 = { version = "0.6.2", features = ["libadwaita"]}
|
||||
libadwaita = {package="libadwaita", version = "0.4.4", path="./libadwaita-0.4.4", features = ["v1_4"]}
|
||||
# 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"] }
|
||||
chrono = "0.4.34"
|
||||
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