Beide Recherchen lagen bisher nur auf lokalen Wegwerf-Branches. Die Kurzfassung steht an den Tickets #2 und #3, die Langfassungen sind mit Quellenangaben und einer Trennung zwischen Belegtem und Vermutetem aber das, worauf man zurueckkommt, wenn spaeter jemand fragt, warum unter Windows Local statt Roaming gewaehlt wurde. Damit sind research/ordner-dialog und research/config-ablageort ueberfluessig und koennen geloescht werden.
537 lines
24 KiB
Markdown
537 lines
24 KiB
Markdown
# Nativer Ordner-Dialog unter Dioxus 0.7 Desktop
|
|
|
|
> Research zu Gitea-Issue #2. Stand: 2026-08-27.
|
|
> Recherchiert gegen Primärquellen (docs.rs, crates.io-API, Quellcode auf GitHub, offizielle Dioxus-Doku).
|
|
> **Nichts davon wurde kompiliert** — im Repo wurde bewusst keine Dependency hinzugefügt. Alles unter
|
|
> „Belegt" ist aus Signaturen/Quellcode/Manifesten abgeleitet, nicht aus einem laufenden Build.
|
|
|
|
## Kurzantwort
|
|
|
|
Die zugrundeliegende Bibliothek ist in beiden Fällen **`rfd` („Rusty File Dialogs"), `0.17.2`** —
|
|
aber es gibt **zwei Wege**, und der bequemere war zunächst übersehen worden:
|
|
|
|
**Weg A — ohne neue Dependency (offiziell dokumentiert).**
|
|
`input { r#type: "file", directory: true, onchange: … }` und der Pfad kommt aus
|
|
`evt.files()[0].path() -> PathBuf`. `dioxus-desktop` ruft dahinter intern
|
|
`rfd::AsyncFileDialog::pick_folder()` auf. Kostet **null** zusätzliche Abhängigkeiten.
|
|
Nachteil: braucht ein DOM-Element und einen echten Nutzer-Klick darauf — nicht aus beliebigem
|
|
Rust-Code heraus auslösbar.
|
|
|
|
**Weg B — `rfd` direkt als eigene Dependency.**
|
|
`rfd::AsyncFileDialog::new().pick_folder().await` in einem `onclick`-Handler. Der einzige Weg für
|
|
einen Dialog, den man **programmatisch** startet. Kostet praktisch nichts, weil `rfd 0.17.2` über
|
|
`dioxus-desktop` ohnehin schon im Dependency-Graph steht (siehe Abschnitt 2).
|
|
|
|
Abbruch liefert in beiden Fällen `None`. Unter Windows läuft beides **ohne Extra-Abhängigkeit und
|
|
ohne Feature-Flag** — nur Linux bringt zusätzliche Anforderungen mit.
|
|
|
|
> **Korrektur zur ersten Fassung dieses Dokuments:** Dort stand „Dioxus selbst bringt keine
|
|
> Datei-/Ordner-Dialog-API mit". **Das war falsch** — der `directory`-Attributweg (Weg A) ist in der
|
|
> offiziellen 0.7-Doku beschrieben. Richtig bleibt: `dioxus-desktop` **re-exportiert** `rfd` nicht,
|
|
> und `dioxus-sdk` hat kein Dialog-Modul.
|
|
|
|
---
|
|
|
|
## 1. Welche Crate?
|
|
|
|
### Belegt
|
|
|
|
- `rfd` ist eine plattformübergreifende Bibliothek für **native** Öffnen-/Speichern-Dialoge und
|
|
bietet „both asynchronous and synchronous APIs". Unterstützte Plattformen laut Crate-Doku:
|
|
Windows, macOS, Linux & BSDs (GTK3 oder XDG Desktop Portal), WASM32 (nur async).
|
|
→ <https://docs.rs/rfd/0.17.2/src/rfd/lib.rs.html>
|
|
- Aktuelle Version: **0.17.2**, veröffentlicht 2026-01-12, ~27 Mio. Downloads gesamt.
|
|
→ <https://crates.io/api/v1/crates/rfd>
|
|
- Repository: <https://github.com/PolyMeilex/rfd>
|
|
- „PickFolder" ist in der offiziellen Feature-Matrix der Crate für **Linux ✔ / Windows ✔ / macOS ✔**
|
|
ausgewiesen (nur Wasm32 ✖).
|
|
→ <https://docs.rs/rfd/0.17.2/src/rfd/lib.rs.html>
|
|
|
|
### Was es *nicht* gibt
|
|
|
|
- **Das offizielle `dioxus-sdk` hat kein Dialog-Modul.** Aktuell `dioxus-sdk 0.7.0` (2025-11-06),
|
|
Repo <https://github.com/DioxusLabs/sdk/>. Die vollständige Feature-Liste der Crate lautet:
|
|
`geolocation`, `notification`, `storage`, `sync`, `time`, `util`, `window` — kein `file`,
|
|
kein `dialog`, kein `fs`.
|
|
→ <https://crates.io/api/v1/crates/dioxus-sdk>
|
|
- **`dioxus-desktop` re-exportiert `rfd` nicht.** Die Crate *benutzt* `rfd` intern (siehe Weg A in
|
|
Abschnitt 3a), aber `mod file_upload;` ist privat und die Re-Export-Liste in `lib.rs` enthält nur
|
|
`tao`, `wry`, `muda`, `trayicon`, `launch`, `AssetRequest`, `Config`, `DesktopContext`,
|
|
`DesktopService`, `WryEventHandler`, `hooks::*`, Shortcut-Typen und `wry::RequestAsyncResponder`.
|
|
Ein `dioxus_desktop::rfd` gibt es also **nicht** — wer `rfd` direkt aufrufen will, muss es selbst
|
|
in die `Cargo.toml` schreiben.
|
|
→ <https://github.com/DioxusLabs/dioxus/blob/v0.7.1/packages/desktop/src/lib.rs>
|
|
|
|
### Korrigiert gegenüber der ersten Fassung
|
|
|
|
Ein reiner Webview-/HTML-Weg **existiert doch** und ist offiziell dokumentiert — er ist nur kein
|
|
„Webview-eigener" Dialog, sondern eine Dioxus-Eigenheit, die auf den nativen `rfd`-Dialog abgebildet
|
|
wird. Siehe Abschnitt 3a.
|
|
|
|
---
|
|
|
|
## 2. Kompatibilität — und: `rfd` ist bereits im Graph
|
|
|
|
### Belegt: die tatsächlich aufgelöste Version ist 0.7.10, nicht 0.7.1
|
|
|
|
`Cargo.toml` deklariert `dioxus = "0.7.1"`, das ist ein Caret-Requirement. Aufgelöst wird in
|
|
`Cargo.lock` tatsächlich:
|
|
|
|
| Crate | Version in `Cargo.lock` |
|
|
|---|---|
|
|
| `dioxus` | **0.7.10** |
|
|
| `dioxus-desktop` | **0.7.10** |
|
|
| `rfd` | **0.17.2** |
|
|
| `tao` | 0.34.8 |
|
|
| `wry` | 0.53.5 |
|
|
| `raw-window-handle` | 0.5.2 **und** 0.6.2 |
|
|
|
|
→ `Cargo.lock` im Repo-Root (Zeilen 2337, 2493, 5847, 6521, 5716/5722).
|
|
|
|
**Das ist der wichtigste praktische Befund:** `dioxus-desktop 0.7.10` hat `rfd` als **nicht-optionale**
|
|
Dependency mit `req = "^0.17.2"`, `default-features = false`, `features = ["xdg-portal"]`, unter
|
|
`cfg(any(windows, macos, linux, dragonfly, freebsd, netbsd, openbsd))`.
|
|
→ <https://crates.io/api/v1/crates/dioxus-desktop/0.7.10/dependencies>
|
|
|
|
Ein `rfd = "0.17"` in unserer `Cargo.toml` fügt dem Graph also **keinen neuen Crate hinzu** und
|
|
kostet **keinen zusätzlichen Compile-Unit** — es macht nur den bereits gebauten Knoten sichtbar.
|
|
Cargo vereinigt die Features additiv; unsere Default-Features von `rfd` sind ohnehin `xdg-portal`,
|
|
also genau das, was `dioxus-desktop` schon setzt.
|
|
|
|
*(Historische Notiz: `dioxus-desktop 0.7.1` hatte noch `rfd ^0.15.3`. Der Sprung auf 0.17 kam
|
|
zwischen 0.7.1 und 0.7.10. Für uns zählt 0.7.10.)*
|
|
|
|
### Belegt: keine Versionskonflikte
|
|
|
|
- `dioxus-desktop 0.7.1` zieht **`tao ^0.34.0`** und **`wry ^0.53.5`**.
|
|
→ <https://crates.io/api/v1/crates/dioxus-desktop/0.7.1/dependencies>
|
|
- `rfd 0.17.2` hat **keinerlei Abhängigkeit auf Dioxus, wry oder tao**. Die einzige gemeinsame
|
|
Berührungsfläche ist `raw-window-handle ^0.6` (unbedingte Dependency von `rfd`).
|
|
→ <https://crates.io/api/v1/crates/rfd/0.17.2/dependencies>
|
|
- `tao 0.34.x` implementiert `HasWindowHandle` und `HasDisplayHandle` aus `raw-window-handle 0.6`
|
|
für `tao::window::Window`, hinter dem Feature `rwh_06`.
|
|
→ <https://docs.rs/tao/0.34.4/tao/window/struct.Window.html>
|
|
- `rwh_06` ist ein **Default-Feature** von `tao` → <https://docs.rs/crate/tao/0.34.4/features>,
|
|
und `dioxus-desktop` schaltet Default-Features nicht ab (`tao = { workspace = true, features = ["rwh_05"] }`
|
|
— additiv, kein `default-features = false`).
|
|
→ <https://github.com/DioxusLabs/dioxus/blob/v0.7.1/packages/desktop/Cargo.toml>
|
|
|
|
**Fazit:** Es gibt keinen Versionskonflikt. `rfd` und `dioxus-desktop` teilen sich `raw-window-handle 0.6`
|
|
und passen semver-kompatibel zusammen.
|
|
|
|
### Vermutung (nicht verifiziert)
|
|
|
|
- Daraus folgt *rechnerisch*, dass `AsyncFileDialog::set_parent(&window)` mit dem Dioxus-Fenster
|
|
typprüfen sollte: `use_window()` liefert `DesktopContext = Rc<DesktopService>`
|
|
(<https://docs.rs/dioxus-desktop/0.7.1/dioxus_desktop/type.DesktopContext.html>,
|
|
<https://docs.rs/dioxus-desktop/0.7.1/dioxus_desktop/fn.use_window.html>), `DesktopService`
|
|
implementiert `Deref<Target = tao::window::Window>`
|
|
(<https://docs.rs/dioxus-desktop/0.7.1/dioxus_desktop/struct.DesktopService.html>), und
|
|
`set_parent` nimmt `&W where W: HasWindowHandle + HasDisplayHandle + ?Sized`
|
|
(<https://docs.rs/rfd/0.17.2/rfd/struct.AsyncFileDialog.html>).
|
|
**Das ist nicht kompiliert worden.** Für einen ersten Wurf ist `set_parent` optional — ohne
|
|
Parent erscheint der Dialog als eigenständiges Fenster statt als besitzer-modaler Dialog.
|
|
|
|
---
|
|
|
|
## 3a. Weg A — der offizielle Weg ohne neue Dependency
|
|
|
|
### Belegt
|
|
|
|
Die offizielle Dioxus-0.7-Doku beschreibt Ordner-Auswahl über ein `directory`-Attribut am
|
|
File-Input:
|
|
|
|
> „Dioxus will map this attribute to browser specific attributes, because there is no standardized
|
|
> way to allow a directory to be selected."
|
|
|
|
```rust
|
|
input {
|
|
type: "file",
|
|
// Select a folder by setting the directory attribute
|
|
directory: true,
|
|
onchange: move |evt| {
|
|
for file in evt.files() {
|
|
println!("{}", file.name());
|
|
}
|
|
}
|
|
}
|
|
```
|
|
→ <https://dioxuslabs.com/learn/0.7/essentials/basics/event_handlers/>
|
|
|
|
Auf dem **Desktop** ist das kein Browser-Dialog, sondern feuert intern exakt den nativen
|
|
`rfd`-Ordner-Dialog:
|
|
|
|
```rust
|
|
// packages/desktop/src/file_upload.rs
|
|
if self.directory {
|
|
if self.multiple {
|
|
dialog.pick_folders().await
|
|
} else {
|
|
dialog.pick_folder().await
|
|
}
|
|
}
|
|
|
|
pub struct DesktopFileData(pub(crate) PathBuf);
|
|
|
|
fn path(&self) -> PathBuf { self.0.clone() }
|
|
```
|
|
→ <https://github.com/DioxusLabs/dioxus/blob/v0.7.10/packages/desktop/src/file_upload.rs>
|
|
|
|
Und der **absolute Pfad** ist öffentlich abgreifbar — `FileData::path()` ist Teil der
|
|
`dioxus-html`-API:
|
|
|
|
```rust
|
|
pub fn path(&self) -> PathBuf
|
|
```
|
|
→ <https://docs.rs/dioxus-html/0.7.10/dioxus_html/struct.FileData.html>
|
|
|
|
### Snippet für Weg A
|
|
|
|
```rust
|
|
use dioxus::prelude::*;
|
|
use std::path::PathBuf;
|
|
|
|
#[component]
|
|
fn WorkspaceOrdnerWahlOhneDependency() -> Element {
|
|
let mut ordner = use_signal(|| None::<PathBuf>);
|
|
|
|
let label = ordner()
|
|
.map(|p| p.display().to_string())
|
|
.unwrap_or_else(|| "Noch kein Ordner gewählt".to_string());
|
|
|
|
rsx! {
|
|
input {
|
|
r#type: "file",
|
|
directory: true,
|
|
onchange: move |evt| {
|
|
// Bei Abbruch ist die Liste leer -> kein Event-File
|
|
if let Some(file) = evt.files().first() {
|
|
ordner.set(Some(file.path()));
|
|
}
|
|
},
|
|
}
|
|
p { "{label}" }
|
|
}
|
|
}
|
|
```
|
|
|
|
### Grenzen von Weg A
|
|
|
|
- Braucht ein **DOM-Element** und einen echten Nutzer-Klick darauf. Ein Ordner-Dialog, den man aus
|
|
Rust heraus programmatisch öffnet (z. B. aus einem Menüeintrag, einem Shortcut oder nach einem
|
|
anderen Ereignis), ist damit **nicht** möglich.
|
|
- Das Styling ist ein `<input type="file">` — für einen hübschen Button muss man das Element
|
|
verstecken und per Label/JS triggern.
|
|
- **Vermutung, nicht verifiziert:** wie sich `evt.files()` bei Abbruch genau verhält (leere Liste
|
|
vs. gar kein `onchange`-Event). Das obige `if let Some(...)` ist gegen beides robust.
|
|
|
|
---
|
|
|
|
## 3b. Blockierend oder async? — Für Weg B im `onclick` zwingend **async**
|
|
|
|
### Belegt: die Signaturen
|
|
|
|
`rfd` hat zwei parallele Typen. Beide bauen per Builder, unterscheiden sich nur im Rückgabetyp:
|
|
|
|
```rust
|
|
// Synchron — rfd::FileDialog
|
|
pub fn pick_folder(self) -> Option<PathBuf>;
|
|
pub fn pick_folders(self) -> Option<Vec<PathBuf>>;
|
|
|
|
// Asynchron — rfd::AsyncFileDialog
|
|
pub fn pick_folder(self) -> impl Future<Output = Option<FileHandle>>;
|
|
pub fn pick_folders(self) -> impl Future<Output = Option<Vec<FileHandle>>>;
|
|
```
|
|
|
|
→ <https://docs.rs/rfd/0.17.2/rfd/struct.FileDialog.html>,
|
|
<https://docs.rs/rfd/0.17.2/rfd/struct.AsyncFileDialog.html>
|
|
|
|
### Belegt: warum die synchrone Variante in Dioxus schadet
|
|
|
|
- Die synchrone Windows-Implementierung fährt die modale COM-Dialogschleife **auf dem aufrufenden
|
|
Thread**:
|
|
|
|
```rust
|
|
fn pick_folder(self) -> Option<PathBuf> {
|
|
fn run(opt: FileDialog) -> Result<PathBuf> {
|
|
init_com(|| {
|
|
let dialog = IDialog::build_pick_folder(&opt)?;
|
|
dialog.show()?; // blockiert hier bis der Nutzer fertig ist
|
|
dialog.get_result()
|
|
})?
|
|
}
|
|
run(self).ok()
|
|
}
|
|
```
|
|
→ <https://github.com/PolyMeilex/rfd/blob/master/src/backend/win_cid/file_dialog.rs>
|
|
|
|
- Dioxus-Event-Handler und via `spawn` gestartete Tasks laufen auf **demselben Thread** wie die
|
|
Event-Loop: `pub fn spawn(fut: impl Future<Output = ()> + 'static) -> Task` — **kein `Send`-Bound**,
|
|
d. h. die Futures können den Thread gar nicht wechseln.
|
|
→ <https://docs.rs/dioxus/0.7.1/dioxus/prelude/fn.spawn.html>
|
|
|
|
Ein `FileDialog::pick_folder()` direkt im `onclick` blockiert also die tao-Event-Loop → Fenster
|
|
friert ein, solange der Dialog offen ist.
|
|
|
|
- Die Crate-Doku selbst empfiehlt, Dialoge auf dem Main-Thread zu *starten*
|
|
(„It is also recommended to spawn dialogs on your main thread") — genau das tut man mit der
|
|
async-Variante aus einem Dioxus-Handler heraus.
|
|
→ <https://docs.rs/rfd/0.17.2/src/rfd/lib.rs.html>
|
|
|
|
### Belegt: warum die async-Variante unter Windows *nicht* blockiert
|
|
|
|
Die Windows-async-Variante lagert den modalen Dialog auf einen eigenen OS-Thread aus und liefert das
|
|
Ergebnis über einen Channel zurück:
|
|
|
|
```rust
|
|
// async_thread(..) — Kern der Implementierung
|
|
std::thread::spawn(move || {
|
|
tx.send(f()).ok();
|
|
});
|
|
|
|
// pick_folder_async
|
|
async_thread(move || Self::pick_folder(self).map(FileHandle::wrap))
|
|
```
|
|
→ <https://github.com/PolyMeilex/rfd/blob/master/src/backend/win_cid/file_dialog.rs>
|
|
|
|
Die Dioxus-Event-Loop läuft weiter; das `.await` gibt den Main-Thread frei, bis der Channel liefert.
|
|
**Kein Tokio-/async-std-Feature nötig** — `rfd` hat keine Runtime-Features, die Implementierung nutzt
|
|
`std::thread` plus Channel. → <https://crates.io/api/v1/crates/rfd/0.17.2/dependencies>
|
|
(keine Runtime-Dependency; nur `log`, `raw-window-handle` unbedingt)
|
|
|
|
Nebenbedingung aus dem offiziellen `rfd`-Beispiel: der Dialog soll **auf dem Main-Thread gestartet**
|
|
(`pick_*()` aufgerufen) und darf anderswo awaited werden — „Spawn dialog on main thread / Await
|
|
somewhere else". → <https://github.com/PolyMeilex/rfd/blob/master/examples/async.rs>
|
|
Im Dioxus-Fall passiert **beides** auf dem Main-Thread (`spawn` hat kein `Send`-Bound), die Bedingung
|
|
ist damit erfüllt.
|
|
|
|
---
|
|
|
|
## 4. Was kommt bei Abbruch zurück?
|
|
|
|
### Belegt
|
|
|
|
**`None`.** Sowohl `FileDialog::pick_folder() -> Option<PathBuf>` als auch
|
|
`AsyncFileDialog::pick_folder() -> impl Future<Output = Option<FileHandle>>` liefern bei Abbruch
|
|
`None`. Es gibt keinen `Result`-Typ und keine Fehlerunterscheidung „abgebrochen vs. fehlgeschlagen" —
|
|
ein technisch fehlgeschlagener Dialog ist von einem abgebrochenen nicht unterscheidbar (die
|
|
Windows-Impl macht am Ende `run(self).ok()`, verwirft den Fehler also).
|
|
→ <https://docs.rs/rfd/0.17.2/rfd/struct.FileDialog.html>,
|
|
<https://docs.rs/rfd/0.17.2/rfd/struct.AsyncFileDialog.html>,
|
|
<https://github.com/PolyMeilex/rfd/blob/master/src/backend/win_cid/file_dialog.rs>
|
|
|
|
Aus dem `FileHandle` kommt man so an den Pfad:
|
|
|
|
```rust
|
|
pub fn path(&self) -> &Path; // existiert nicht unter WASM32
|
|
pub fn file_name(&self) -> String;
|
|
// zusätzlich: impl From<FileHandle> for PathBuf
|
|
```
|
|
→ <https://docs.rs/rfd/0.17.2/rfd/struct.FileHandle.html>
|
|
|
|
---
|
|
|
|
## 5. Plattformen und Feature-Flags
|
|
|
|
### Belegt: Windows — out of the box, ja
|
|
|
|
Einzige Windows-Dependency von `rfd 0.17.2`:
|
|
|
|
| Target | Crate | Version | Features |
|
|
|---|---|---|---|
|
|
| `cfg(target_os = "windows")` | `windows-sys` | `^0.61` | `Win32_Foundation`, `Win32_System_Com`, `Win32_UI_Shell_Common`, `Win32_UI_Shell`, `Win32_UI_WindowsAndMessaging` |
|
|
|
|
→ <https://crates.io/api/v1/crates/rfd/0.17.2/dependencies>
|
|
|
|
`windows-sys` ist reines Rust-Binding auf die Systembibliotheken — **keine C-Header, kein vcpkg,
|
|
kein Build-Tool, kein Feature-Flag**. Zusammen mit der Implementierung über `IFileOpenDialog`/COM
|
|
(siehe `init_com` / `IDialog::build_pick_folder` oben) heißt das:
|
|
**`rfd = "0.17"` in die `Cargo.toml`, fertig.**
|
|
|
|
Optional unter Windows: das Feature `common-controls-v6` — betrifft ausschließlich
|
|
**Message-Dialoge** (`TaskDialogIndirect` aus ComCtl32.dll v6, für eigene Button-Texte) und braucht
|
|
zusätzlich ein Application-Manifest. **Für Ordner-Dialoge irrelevant.**
|
|
→ <https://docs.rs/rfd/0.17.2/src/rfd/lib.rs.html>
|
|
|
|
### Belegt: macOS
|
|
|
|
Reine Rust/objc2-Dependencies (`objc2`, `objc2-app-kit`, `objc2-foundation`, `block2`, `dispatch2`),
|
|
keine Feature-Flags nötig. Einschränkung aus der Crate-Doku:
|
|
|
|
> „macOS async dialogs require an `NSApplication` instance, so the dialog is only truly async when
|
|
> opened in windowed environment like `winit` or `SDL2`. Otherwise, it will fallback to sync dialog."
|
|
|
|
Eine Dioxus-Desktop-App **ist** eine windowed App (tao/`NSApplication`), die Bedingung ist also
|
|
erfüllt. → <https://docs.rs/rfd/0.17.2/src/rfd/lib.rs.html>,
|
|
<https://crates.io/api/v1/crates/rfd/0.17.2/dependencies>
|
|
|
|
### Belegt: Linux — hier steckt der einzige echte Stolperstein
|
|
|
|
Zwei Backends, per Cargo-Feature wählbar:
|
|
|
|
- **`xdg-portal` (Default)** — XDG Desktop Portal über D-Bus. Braucht zur **Laufzeit** `libdbus`
|
|
(fällt sonst auf `zenity` zurück) **und** ein installiertes Portal-Backend (GTK, GNOME oder KDE).
|
|
Das wlroots-Portal-Backend implementiert die benötigte D-Bus-API **nicht**.
|
|
- **`gtk3`** — `default-features = false, features = ["gtk3"]`. Braucht die GTK3-**Entwicklungs**-
|
|
Header schon zur **Build**-Zeit (`libgtk-3-dev` / `gtk3-devel` / `pacman -S gtk3`).
|
|
|
|
→ <https://docs.rs/rfd/0.17.2/src/rfd/lib.rs.html>
|
|
|
|
**Also: nein, „derselbe Aufruf überall" gilt für den *Rust-Code*, aber nicht für das *Packaging*.**
|
|
Der Aufruf im Code ist auf allen drei Plattformen identisch; Linux-Builds brauchen zusätzlich eine
|
|
Aussage darüber, welches Backend gewählt wird und was beim Nutzer installiert sein muss.
|
|
Für DioNotes auf Windows 11 ist das derzeit kein Thema.
|
|
|
|
---
|
|
|
|
## 6. Lauffähiges Beispiel-Snippet für Weg B (Dioxus 0.7 API)
|
|
|
|
`Cargo.toml` — **nicht angewendet**, gehört in ein Implementierungs-Ticket:
|
|
|
|
```toml
|
|
[dependencies]
|
|
rfd = "0.17" # bereits transitiv via dioxus-desktop 0.7.10 im Lock-File
|
|
```
|
|
|
|
Komponente:
|
|
|
|
```rust
|
|
use dioxus::prelude::*;
|
|
use std::path::PathBuf;
|
|
|
|
#[component]
|
|
fn WorkspaceOrdnerWahl() -> Element {
|
|
let mut ordner = use_signal(|| None::<PathBuf>);
|
|
|
|
// Anzeige-Text vorberechnen, damit im rsx! kein Ausdruck nötig ist
|
|
let label = ordner()
|
|
.map(|p| p.display().to_string())
|
|
.unwrap_or_else(|| "Noch kein Ordner gewählt".to_string());
|
|
|
|
rsx! {
|
|
button {
|
|
// Async-Closures in Event-Handlern werden von Dioxus automatisch gespawnt.
|
|
onclick: move |_| async move {
|
|
let auswahl = rfd::AsyncFileDialog::new()
|
|
.set_title("Workspace-Ordner wählen")
|
|
.pick_folder()
|
|
.await;
|
|
|
|
match auswahl {
|
|
// FileHandle::path() -> &Path
|
|
Some(handle) => ordner.set(Some(handle.path().to_path_buf())),
|
|
// Nutzer hat abgebrochen -> None; Zustand bewusst unverändert lassen
|
|
None => {}
|
|
}
|
|
},
|
|
"Ordner wählen…"
|
|
}
|
|
p { "{label}" }
|
|
}
|
|
}
|
|
```
|
|
|
|
Belege für die Dioxus-Seite des Snippets:
|
|
|
|
- Async-Closure direkt als Event-Handler: „Async closures passed to event handlers are automatically
|
|
spawned" — offizielles Beispiel in der 0.7-Doku.
|
|
→ <https://dioxuslabs.com/learn/0.7/llms-full.txt> (Abschnitt „Use async closures in event handlers")
|
|
- Alternative, falls die Async-Closure irgendwo Ärger macht — `spawn` explizit:
|
|
```rust
|
|
onclick: move |_| {
|
|
spawn(async move {
|
|
if let Some(h) = rfd::AsyncFileDialog::new().pick_folder().await {
|
|
ordner.set(Some(h.path().to_path_buf()));
|
|
}
|
|
});
|
|
},
|
|
```
|
|
→ <https://docs.rs/dioxus/0.7.1/dioxus/prelude/fn.spawn.html>,
|
|
<https://dioxuslabs.com/learn/0.7/llms-full.txt> (Abschnitt „Spawn background tasks in event handlers")
|
|
- `use_signal` / `.set()` statt `use_state`: entspricht der 0.7-API in `AGENTS.md`.
|
|
|
|
### Optionale Erweiterung (Vermutung, nicht kompiliert)
|
|
|
|
Damit der Dialog besitzer-modal am App-Fenster hängt:
|
|
|
|
```rust
|
|
let window = use_window(); // DesktopContext = Rc<DesktopService>, Deref -> tao::Window
|
|
// ...
|
|
rfd::AsyncFileDialog::new()
|
|
.set_parent(&*window) // &DesktopService --deref--> &tao::window::Window
|
|
.set_title("Workspace-Ordner wählen")
|
|
.pick_folder()
|
|
.await
|
|
```
|
|
|
|
Typtheoretisch geht das auf (siehe Abschnitt 2), **aber ungetestet**. Zwei offene Punkte:
|
|
|
|
1. Ob die Deref-Kette bis `tao::window::Window` vom Compiler an `set_parent`s Bound
|
|
`W: HasWindowHandle + HasDisplayHandle + ?Sized` angepasst wird, oder ob ein expliziter
|
|
`&*(*window)` / Zwischenschritt nötig ist.
|
|
2. Windows führt den async-Dialog auf einem **anderen** Thread aus (Abschnitt 3). Ein Parent-HWND
|
|
über Thread-Grenzen hinweg ist unter Win32 grundsätzlich zulässig, das Modalitäts-Verhalten
|
|
(Deaktivierung des Owner-Fensters) sollte man aber praktisch prüfen.
|
|
|
|
Empfehlung: **erst ohne `set_parent` implementieren**, das ist die belegte Variante.
|
|
|
|
---
|
|
|
|
## 7. Empfehlung für DioNotes
|
|
|
|
Für „Ordner beim Anlegen eines Workspace wählen" ist die Auswahl eine reine UX-Frage:
|
|
|
|
- Wenn der Dialog aus einem **sichtbaren Button/Input im Formular** kommt → **Weg A**,
|
|
null neue Dependencies, offiziell dokumentiert.
|
|
- Wenn der Dialog **programmatisch** aufgehen soll (Menü, Shortcut, Folgeschritt eines Wizards)
|
|
→ **Weg B** mit `rfd = "0.17"`. Kostet dank des schon vorhandenen Lock-Eintrags praktisch nichts.
|
|
|
|
Beide landen unter Windows im selben nativen `IFileOpenDialog`.
|
|
|
|
## 8. Zusammenfassung der offenen Punkte
|
|
|
|
| Punkt | Status |
|
|
|---|---|
|
|
| Crate + Version | **belegt** — `rfd 0.17.2` |
|
|
| `rfd` bereits transitiv im Lock-File (via dioxus-desktop 0.7.10) | **belegt** (`Cargo.lock` + crates.io) |
|
|
| Projekt löst `dioxus 0.7.1` real auf **0.7.10** auf | **belegt** (`Cargo.lock`) |
|
|
| Weg A: `directory: true` + `FileData::path()` | **belegt** (offizielle Doku + docs.rs + Quellcode) |
|
|
| Weg A nutzt intern `rfd::…::pick_folder()` | **belegt** (`packages/desktop/src/file_upload.rs`) |
|
|
| Kein Konflikt mit tao 0.34 / wry 0.53 | **belegt** (Manifeste) |
|
|
| async statt sync in `onclick` (Weg B) | **belegt** (Signaturen + Quellcode + `spawn`-Signatur ohne `Send`) |
|
|
| Abbruch → `None` | **belegt** |
|
|
| Windows out of the box, kein Feature-Flag | **belegt** (nur `windows-sys ^0.61`) |
|
|
| Linux braucht Portal-Backend bzw. `gtk3`-Header | **belegt** |
|
|
| `dioxus-sdk` hat kein Dialog-Modul | **belegt** (Feature-Liste 0.7.0) |
|
|
| `dioxus_desktop::rfd` existiert nicht (kein Re-Export) | **belegt** (`lib.rs`) |
|
|
| `set_parent` mit dem Dioxus-Fenster | **Vermutung** — plausibel, nicht kompiliert |
|
|
| `evt.files()`-Verhalten bei Abbruch in Weg A | **Vermutung** — Snippet ist gegen beide Varianten robust |
|
|
| Verhalten bei Netzlaufwerken / UNC-Pfaden | **nicht recherchiert** |
|
|
| Verhalten unter `dx serve` / Hot-Reload | **nicht recherchiert** |
|
|
|
|
## Quellenliste
|
|
|
|
- rfd Crate-Doku (Quellcode-Ansicht der `//!`-Doku): <https://docs.rs/rfd/0.17.2/src/rfd/lib.rs.html>
|
|
- `rfd::FileDialog`: <https://docs.rs/rfd/0.17.2/rfd/struct.FileDialog.html>
|
|
- `rfd::AsyncFileDialog`: <https://docs.rs/rfd/0.17.2/rfd/struct.AsyncFileDialog.html>
|
|
- `rfd::FileHandle`: <https://docs.rs/rfd/0.17.2/rfd/struct.FileHandle.html>
|
|
- rfd Windows-Backend (Quellcode): <https://github.com/PolyMeilex/rfd/blob/master/src/backend/win_cid/file_dialog.rs>
|
|
- rfd Crate-Metadaten: <https://crates.io/api/v1/crates/rfd>
|
|
- rfd async-Beispiel: <https://github.com/PolyMeilex/rfd/blob/master/examples/async.rs>
|
|
- dioxus-sdk Crate-Metadaten (Feature-Liste): <https://crates.io/api/v1/crates/dioxus-sdk>
|
|
- rfd 0.17.2 Dependencies: <https://crates.io/api/v1/crates/rfd/0.17.2/dependencies>
|
|
- dioxus-desktop 0.7.1 Dependencies: <https://crates.io/api/v1/crates/dioxus-desktop/0.7.1/dependencies>
|
|
- dioxus-desktop **0.7.10** Dependencies (die real aufgelöste Version): <https://crates.io/api/v1/crates/dioxus-desktop/0.7.10/dependencies>
|
|
- dioxus-desktop `file_upload.rs` (interner rfd-Aufruf) @ v0.7.10: <https://github.com/DioxusLabs/dioxus/blob/v0.7.10/packages/desktop/src/file_upload.rs>
|
|
- dioxus-desktop `lib.rs` (Re-Export-Liste) @ v0.7.1: <https://github.com/DioxusLabs/dioxus/blob/v0.7.1/packages/desktop/src/lib.rs>
|
|
- `dioxus_html::FileData` (`path()`): <https://docs.rs/dioxus-html/0.7.10/dioxus_html/struct.FileData.html>
|
|
- Dioxus 0.7 Doku, `directory`-Attribut: <https://dioxuslabs.com/learn/0.7/essentials/basics/event_handlers/>
|
|
- dioxus-desktop `Cargo.toml` @ v0.7.1: <https://github.com/DioxusLabs/dioxus/blob/v0.7.1/packages/desktop/Cargo.toml>
|
|
- `DesktopService`: <https://docs.rs/dioxus-desktop/0.7.1/dioxus_desktop/struct.DesktopService.html>
|
|
- `DesktopContext`: <https://docs.rs/dioxus-desktop/0.7.1/dioxus_desktop/type.DesktopContext.html>
|
|
- `use_window`: <https://docs.rs/dioxus-desktop/0.7.1/dioxus_desktop/fn.use_window.html>
|
|
- `dioxus::prelude::spawn`: <https://docs.rs/dioxus/0.7.1/dioxus/prelude/fn.spawn.html>
|
|
- tao 0.34.4 `Window` (rwh-Impls): <https://docs.rs/tao/0.34.4/tao/window/struct.Window.html>
|
|
- tao 0.34.4 Feature-Liste: <https://docs.rs/crate/tao/0.34.4/features>
|
|
- Dioxus 0.7 Doku (Async, Desktop): <https://dioxuslabs.com/learn/0.7> / <https://dioxuslabs.com/learn/0.7/llms-full.txt>
|