Slint Kit Documentation

Fichiers

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.

Cible de dépôt dont la disponibilité suit les capacités de l’application hôte.
Application native de l’exemple ci-dessous.

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

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
Cible de dépôt dont la disponibilité suit les capacités de l’application hôte.
Capture native du même exemple, moteur Skia.
// 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; }
    }
}

Télécharger cet exemple

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

Voir aussi