Zone de dépôt de fichiers pour Slint
KitFileDropZone · Slint Kit 0.3.1
Cible de dépôt dont la disponibilité suit les capacités de l’application hôte.

Les dialogues de fichiers et le dépôt depuis le système nécessitent l’application native ci-dessous.
Essayer le composant
Télécharger le projet complet, le décompresser et lancer cargo run --locked dans son dossier. Rust 1.92+ ; Slint Kit 0.3.0 depuis crates.io. Vous pouvez aussi copier les quatre fichiers ci-dessous.
Sur macOS ou Windows natif, les événements de dépôt ajoutent des références de fichiers typées. Choisir des fichiers fournit un sélecteur natif de secours. Le navigateur indique une absence de prise en charge.
ui/app.slint
export { KitFormValidation, HtmlInputKind, NumberInputFormat, KitInputConstraints, KitValidationResult, KitValidityState } from "@kit/kit.slint";
import {KitFilePicker,KitFileDropZone,KitThemes,FileViewEntry,FileSelectionMode,HostRequestPresentation,DropCapability} from "@kit/kit.slint";
export {FileViewEntry,FileSelectionMode,HostRequestPresentation,DropCapability}
export component App inherits Window{
title:"file-drop-zone";preferred-width:580px;preferred-height:480px;max-width:100000px;max-height:100000px;background:KitThemes.light.canvas;
in property <[FileViewEntry]> entries;in property <HostRequestPresentation> request-state;in property <string> error;in property <string> status;
callback browse(FileSelectionMode);callback remove(int,int);in property <DropCapability> drop-capability;in property <bool> hovered;callback can-drop(DropEvent)->DragAction;callback dropped(DropEvent)->DragAction;
VerticalLayout{padding:24px;spacing:16px;alignment:start;drop-zone:=KitFileDropZone{capability:root.drop-capability;host-drag-hovered:root.hovered;can-drop(event)=>{return root.can-drop(event);}dropped(event)=>{return root.dropped(event);}}
control:=KitFilePicker{label:"Selected files";description:"Names only; file content is not read.";mode:FileSelectionMode.multiple;entries:root.entries;request-state:root.request-state;error:root.error;browse-requested(mode)=>{root.browse(mode);}remove-requested(token,generation)=>{root.remove(token,generation);}}
Text{text:root.status;color:KitThemes.light.text-primary;wrap:word-wrap;}
}
}
src/main.rs
use slint::ComponentHandle;
use slint_kit::{
FileHandle, FileRequest, HostCapability, HostError, HostRequestController, SelectionOutcome,
};
use std::{cell::RefCell, rc::Rc};
mod ui {
slint::include_modules!();
}
#[derive(Clone, Copy, PartialEq)]
struct FileId(i32);
struct Entry {
id: FileId,
handle: FileHandle,
}
struct Files {
generation: i32,
next_id: i32,
entries: Vec<Entry>,
requests: HostRequestController<Vec<FileHandle>>,
}
fn publish(app: &ui::App, state: &Files) {
app.set_entries(slint::ModelRc::new(slint::VecModel::from(
state
.entries
.iter()
.map(|entry| ui::FileViewEntry {
token: entry.id.0,
generation: state.generation,
name: entry.handle.display_name.as_str().into(),
detail: "Selected resource".into(),
removable: true,
})
.collect::<Vec<_>>(),
)));
app.set_status(format!("{} selected files", state.entries.len()).into());
}
fn replace(state: &mut Files, files: Vec<FileHandle>) {
state.entries.clear();
for handle in files {
let id = FileId(state.next_id);
state.next_id = state.next_id.checked_add(1).expect("resource identity");
state.entries.push(Entry { id, handle });
}
state.generation = state.generation.checked_add(1).expect("view generation");
}
fn failed(app: &ui::App, error: HostError) {
app.set_request_state(if error == HostError::Unsupported {
ui::HostRequestPresentation::Unsupported
} else {
ui::HostRequestPresentation::Failed
});
app.set_error(format!("{error:?}").into());
}
fn main() -> Result<(), slint::PlatformError> {
let app = ui::App::new()?;
slint_kit::init_form_validation!(ui, app);
let state = Rc::new(RefCell::new(Files {
generation: 1,
next_id: 1,
entries: Vec::new(),
requests: HostRequestController::default(),
}));
publish(&app, &state.borrow());
if slint_kit::native::capabilities().files == HostCapability::Unsupported {
app.set_request_state(ui::HostRequestPresentation::Unsupported);
}
let weak = app.as_weak();
let shared = state.clone();
app.on_browse(move |mode| {
let Some(app) = weak.upgrade() else {
return;
};
let ticket = shared.borrow_mut().requests.begin();
app.set_request_state(ui::HostRequestPresentation::Pending);
app.set_error("".into());
let shared_async = shared.clone();
let weak_async = weak.clone();
if slint::spawn_local(async move {
let outcome = slint_kit::native::pick_files(FileRequest {
selection: if mode == ui::FileSelectionMode::Single {
slint_kit::FileSelectionMode::Single
} else {
slint_kit::FileSelectionMode::Multiple
},
filters: Vec::new(),
suggested_name: None,
})
.await;
let Some(app) = weak_async.upgrade() else {
return;
};
let mut state = shared_async.borrow_mut();
if !state.requests.finish(ticket, outcome) {
return;
}
match state.requests.take_result().unwrap() {
SelectionOutcome::Selected(files) => {
replace(&mut state, files);
app.set_request_state(ui::HostRequestPresentation::Idle);
publish(&app, &state);
}
SelectionOutcome::Cancelled => {
app.set_request_state(ui::HostRequestPresentation::Cancelled)
}
SelectionOutcome::Failed(error) => failed(&app, error),
}
})
.is_err()
{
shared.borrow_mut().requests.cancel();
failed(&app, HostError::Unavailable);
}
});
let weak = app.as_weak();
let shared = state.clone();
app.on_remove(move |token, generation| {
if let Some(app) = weak.upgrade() {
let mut state = shared.borrow_mut();
if state.generation != generation {
return;
}
state.requests.cancel();
state.entries.retain(|entry| entry.id != FileId(token));
state.generation = state.generation.checked_add(1).expect("view generation");
app.set_request_state(ui::HostRequestPresentation::Idle);
publish(&app, &state);
}
});
use slint::language::DragAction;
#[cfg(not(target_arch = "wasm32"))]
{
use slint::winit_030::{WinitWindowAccessor, winit::event::WindowEvent};
let weak = app.as_weak();
let shared = state.clone();
app.window().on_winit_window_event(move |native, event| {
if let Some(app) = weak.upgrade() {
if cfg!(any(target_os = "macos", target_os = "windows"))
&& native.has_winit_window()
{
app.set_drop_capability(ui::DropCapability::Available);
}
match event {
WindowEvent::HoveredFile(_) => app.set_hovered(true),
WindowEvent::HoveredFileCancelled => app.set_hovered(false),
WindowEvent::DroppedFile(path) => {
let mut state = shared.borrow_mut();
state.requests.cancel();
let id = FileId(state.next_id);
state.next_id = state.next_id.checked_add(1).expect("file identity");
state.entries.push(Entry {
id,
handle: slint_kit::FileHandle {
display_name: path
.file_name()
.unwrap_or_default()
.to_string_lossy()
.into_owned(),
location: slint_kit::FileLocation::Path(path.to_owned()),
byte_length: None,
},
});
state.generation =
state.generation.checked_add(1).expect("view generation");
app.set_hovered(false);
publish(&app, &state);
}
_ => {}
}
}
slint::winit_030::EventResult::Propagate
});
}
let weak = app.as_weak();
app.on_can_drop(move |event| {
if weak
.upgrade()
.is_some_and(|app| app.get_drop_capability() == ui::DropCapability::Available)
&& event.data.has_file_paths()
{
DragAction::Copy
} else {
DragAction::None
}
});
let weak = app.as_weak();
let shared = state.clone();
app.on_dropped(move |event| {
let Some(app) = weak.upgrade() else {
return DragAction::None;
};
if app.get_drop_capability() != ui::DropCapability::Available {
return DragAction::None;
}
let Ok(paths) = event.data.file_paths() else {
return DragAction::None;
};
let files = paths
.map(|path| slint_kit::FileHandle {
display_name: path
.file_name()
.unwrap_or_default()
.to_string_lossy()
.into_owned(),
location: slint_kit::FileLocation::Path(path.to_owned()),
byte_length: None,
})
.collect::<Vec<_>>();
if files.is_empty() {
return DragAction::None;
}
let mut state = shared.borrow_mut();
state.requests.cancel();
for handle in files {
let id = FileId(state.next_id);
state.next_id = state.next_id.checked_add(1).expect("file identity");
state.entries.push(Entry { id, handle });
}
state.generation = state.generation.checked_add(1).expect("view generation");
publish(&app, &state);
DragAction::Copy
});
app.run()
}
Cargo.toml et build.rs
Cargo.toml
[package]
name = "slint-kit-file-drop-zone-example"
version = "0.1.0"
edition = "2024"
rust-version = "1.92"
license = "GPL-3.0-or-later"
publish = false
[dependencies]
slint-kit = { version = "=0.3.0", features = ["native"] }
slint = { version = "=1.18.1", default-features = false, features = ["std", "compat-1-18", "backend-winit", "renderer-skia", "accessibility", "unstable-winit-030"] }
[build-dependencies]
slint-kit-build = "=0.3.0"
[workspace]
build.rs
fn main() {
slint_kit_build::compile("ui/app.slint").expect("compile component example");
}
Fichiers : Cargo.toml · build.rs · ui/app.slint · src/main.rs
Reliez les vrais événements de dépôt à FileHandle et publiez DropCapability. L’exemple ajoute un sélecteur natif de repli.
Le pont Winit gère les chemins sur macOS et Windows. Les autres moteurs et le navigateur affichent une absence de prise en charge ; le dépôt externe exige un hôte natif.
Choisir entre une option intégrée et une composition personnalisée
Options partagées
label— propriété intégrée ; exemple de composition personnaliséerror— propriété intégrée ; exemple de composition personnalisé
Composition visuelle
Le fichier ci-dessous produit la capture. Pour les composants pilotés par un contrôleur, il montre l’état affiché sans fournir le branchement Rust. Créer un projet.
Afficher le fichier de la capture

// Public label overrides localize this fixture; toolkit/host defaults keep their language.
// SPDX-License-Identifier: GPL-3.0-or-later
import { DropCapability, KitButton, KitFileDropZone, KitTheme, KitThemes } from "@kit/kit.slint";
export component Example inherits Window {
title: "KitFileDropZone — exemple";
preferred-width: 520px;
preferred-height: 270px;
max-width: 100000px;
max-height: 100000px;
background: root.theme.canvas;
in-out property <KitTheme> theme: KitThemes.light;
in-out property <string> status;
VerticalLayout {
padding: 24px; spacing: 16px; alignment: start;
Text { text: "Zone de dépôt de fichiers"; color: root.theme.text-primary; font-size: 20px * root.theme.text-scale; font-weight: 600; }
KitFileDropZone { label: "Déposez les fichiers ici"; unsupported-label: "Le glisser-déposer est indisponible ; utilisez Choisir des fichiers."; theme: root.theme; capability: DropCapability.unsupported; } KitButton { theme: root.theme; text: "Choisir des fichiers…"; activated => { root.status = "Action demandée"; } }
if root.status != "": Text { text: root.status; color: root.theme.text-secondary; font-size: 12px * root.theme.text-scale; wrap: word-wrap; }
}
}
API publique
Hérite de Rectangle.
Propriétés
| Nom | Type | Sens |
|---|---|---|
theme |
KitTheme |
in |
control-size |
ControlSize |
in |
access |
FieldAccess |
in |
capability |
DropCapability |
in |
label |
string |
in |
unsupported-label |
string |
in |
error |
string |
in |
host-drag-hovered |
bool |
in |
drag-hovered |
bool |
out |
Types et variantes
| Type | Déclaration |
|---|---|
enum ControlSize |
compact, medium, comfortable |
enum DropCapability |
unsupported, available |
enum FieldAccess |
editable, read-only, disabled |
Signaux
can-drop(DropEvent) → DragActiondropped(DropEvent) → DragAction