Slint Kit Documentation

Choix et valeurs

Sélection multiple pour Slint

KitMultiSelect · Slint Kit 0.3.1

Sélecteur multiple avec des étiquettes amovibles pour les valeurs retenues.

Sélecteur multiple avec des étiquettes amovibles pour les valeurs retenues.
Application native de l’exemple 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.

Choisissez Push pour l’ajouter sans perdre Email. Retirez une pastille ou désactivez un canal accepté.

ui/app.slint

export { KitFormValidation, HtmlInputKind, NumberInputFormat, KitInputConstraints, KitValidationResult, KitValidityState } from "@kit/kit.slint";
// SPDX-License-Identifier: GPL-3.0-or-later
import { KitMultiSelect, OptionRow, ChoiceRow, ChipRow, SelectState, NavigationStep, SelectionAvailability, KitTheme, KitThemes } from "@kit/kit.slint";
export component App inherits Window {
    title: "KitMultiSelect example";
    preferred-width: 520px;
    preferred-height: 400px;
    max-width: 100000px;
    max-height: 100000px;
    background: root.theme.canvas;
    in-out property <KitTheme> theme: KitThemes.light;
    in property <[OptionRow]> rows;
    in property <[ChoiceRow]> choices;
    in property <[ChipRow]> chips;
    in property <SelectState> select-state;
    in property <string> result;
    callback toggle(int, int);
    callback navigate(NavigationStep);
    callback open();
    callback close();
    callback remove(int);
    callback query(string);
    callback typeahead(string);
    VerticalLayout {
        padding: 24px;
        spacing: 16px;
        alignment: start;
        control := KitMultiSelect {
            theme: root.theme; rows: root.rows; chips: root.chips; state: root.select-state; placeholder: "Add a channel";
            open-requested => { root.open(); }
            navigate(step) => { root.navigate(step); }
            activate(token, generation) => { root.toggle(token, generation); }
            dismiss(reason) => { root.close(); }
            tab-navigation(reverse) => { return reject; }
            query-edited(text) => { root.query(text); }
            typeahead(text) => { root.typeahead(text); }
            remove-requested(token) => { root.remove(token); }
        }
        result := Text { text: root.result; color: root.theme.text-primary; }
    }

}

src/main.rs

// SPDX-License-Identifier: GPL-3.0-or-later
use slint::{ComponentHandle, ModelRc, VecModel};
use slint_kit::{MultiSelectController, Ownership, SelectOption, NavigationStep};
use std::{cell::RefCell, rc::Rc};
mod ui { slint::include_modules!(); }
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
enum Channel { Email, Push, Sms }
impl Channel {
    fn all() -> [Self; 3] { [Self::Email, Self::Push, Self::Sms] }
    fn token(self) -> i32 { match self { Self::Email => 11, Self::Push => 23, Self::Sms => 38 } }
    fn from_token(token: i32) -> Option<Self> { Self::all().into_iter().find(|key| key.token() == token) }
    fn label(self) -> &'static str { match self { Self::Email => "Email", Self::Push => "Push", Self::Sms => "SMS" } }
}
fn publish(app: &ui::App, controller: &MultiSelectController<Channel>, open: bool) {
    let visible = controller.options().iter().map(|option| option.key).collect::<Vec<_>>();
    let rows = visible.iter().copied().map(|key| ui::OptionRow { token: key.token(), generation: 1, label: key.label().into(), description: "".into(), disabled: false });
    let choices = Channel::all().map(|key| ui::ChoiceRow { token: key.token(), label: key.label().into(), disabled: false, selected: controller.selected().contains(&key) });
    let chips = controller.selected().iter().map(|key| ui::ChipRow { token: key.token(), label: key.label().into(), removable: true }).collect::<Vec<_>>();
    let active = controller.active().copied().unwrap_or(Channel::Email);
    app.set_rows(ModelRc::new(VecModel::from(rows.collect::<Vec<_>>()))); app.set_choices(ModelRc::new(VecModel::from(choices.to_vec()))); app.set_chips(ModelRc::new(VecModel::from(chips)));
    app.set_select_state(ui::SelectState { open, generation: 1, active_index: visible.iter().position(|key| *key == active).map_or(-1, |index| index as i32), active: ui::SelectionRef { present: controller.active().is_some(), token: active.token() }, selected: ui::SelectionRef { present: false, token: -1 }, selection_availability: ui::SelectionAvailability::Absent });
    app.set_result(format!("Selected: {}", controller.selected().iter().map(|key| key.label()).collect::<Vec<_>>().join(", ")).into());
}
fn step(value: ui::NavigationStep) -> NavigationStep { match value { ui::NavigationStep::Next => NavigationStep::Next, ui::NavigationStep::Previous => NavigationStep::Previous, ui::NavigationStep::First => NavigationStep::First, ui::NavigationStep::Last => NavigationStep::Last, ui::NavigationStep::PageNext => NavigationStep::PageNext, ui::NavigationStep::PagePrevious => NavigationStep::PagePrevious } }
fn main() -> Result<(), slint::PlatformError> {
    let app = ui::App::new()?;
    slint_kit::init_form_validation!(ui, app);
    let controller = Rc::new(RefCell::new(MultiSelectController::new(Channel::all().into_iter().map(|key| SelectOption::new(key, key.label())).collect(), vec![Channel::Email], Ownership::Local, None).expect("unique channel keys")));
    controller.borrow_mut().navigate(NavigationStep::First, 3);
    publish(&app, &controller.borrow(), false);
    let state = controller.clone(); let weak = app.as_weak();
    app.on_open(move || { if let Some(app) = weak.upgrade() { publish(&app, &state.borrow(), true); } });
    let state = controller.clone(); let weak = app.as_weak();
    app.on_close(move || { if let Some(app) = weak.upgrade() { publish(&app, &state.borrow(), false); } });
    let state = controller.clone(); let weak = app.as_weak();
    app.on_navigate(move |direction| { state.borrow_mut().navigate(step(direction), 3); if let Some(app) = weak.upgrade() { publish(&app, &state.borrow(), app.get_select_state().open); } });
    let state = controller.clone(); let weak = app.as_weak();
    app.on_toggle(move |token, generation| {
        if generation != 1 { return; }
        if let Some(key) = Channel::from_token(token) { let _ = state.borrow_mut().toggle(&key); }
        if let Some(app) = weak.upgrade() { publish(&app, &state.borrow(), true); }
    });
    let state = controller.clone(); let weak = app.as_weak();
    let query = Rc::new(RefCell::new(String::new()));
    let draft = query.clone();
    app.on_query(move |text| {
        *draft.borrow_mut() = text.to_string();
        let needle = text.trim().to_lowercase();
        let options = Channel::all().into_iter().filter(|key| key.label().to_lowercase().contains(&needle)).map(|key| SelectOption::new(key, key.label())).collect();
        let mut controller = state.borrow_mut();
        controller.set_options(options).expect("unique channel keys");
        controller.navigate(NavigationStep::First, 3);
        if let Some(app) = weak.upgrade() { publish(&app, &controller, true); }
    });
    let weak = app.as_weak();
    app.on_typeahead(move |text| {
        query.borrow_mut().push_str(&text);
        let text = query.borrow().clone();
        if let Some(app) = weak.upgrade() { app.invoke_query(text.into()); }
    });
    let weak = app.as_weak();
    app.on_remove(move |token| {
        if let Some(key) = Channel::from_token(token) { let accepted = controller.borrow().selected().iter().copied().filter(|selected| *selected != key).collect(); let _ = controller.borrow_mut().set_selected(accepted); }
        if let Some(app) = weak.upgrade() { publish(&app, &controller.borrow(), app.get_select_state().open); }
    });
    app.run()
}

Cargo.toml et build.rs

Cargo.toml

[package]
name = "slint-kit-multi-select-example"
version = "0.1.0"
edition = "2024"
rust-version = "1.92"
license = "GPL-3.0-or-later"
publish = false

[dependencies]
slint-kit = "=0.3.0"
slint = { version = "=1.18.1", default-features = false, features = ["std", "compat-1-18", "backend-winit", "renderer-skia", "accessibility"] }

[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

Projetez les options de MultiSelectController, les étiquettes retenues et SelectState. Traitez séparément activate et remove-requested ; reliez query-edited et typeahead au filtrage sans remplacer la sélection.

La requête ne remplace pas l’ensemble des choix acceptés. En lecture seule, les valeurs sont présentées dans des champs natifs sélectionnables et les modifications sont bloqué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
Sélecteur multiple avec des étiquettes amovibles pour les valeurs retenues.
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 { KitMultiSelect, KitTheme, KitThemes, SelectionAvailability } from "@kit/kit.slint";

export component Example inherits Window {
    title: "KitMultiSelect — exemple";
    preferred-width: 420px;
    preferred-height: 220px;
    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: "Sélection multiple"; color: root.theme.text-primary; font-size: 20px * root.theme.text-scale; font-weight: 600; }
        KitMultiSelect { placeholder: "Ajouter une sélection"; width: 372px; theme: root.theme; rows: [{ token: 11, generation: 1, label: "Vue d’ensemble", description: "Résumé du projet", disabled: false }, { token: 23, generation: 1, label: "Activité", description: "Modifications récentes", disabled: false }, { token: 38, generation: 1, label: "Réglages", description: "Préférences du projet", disabled: false }]; chips: [{ token: 11, label: "Design", removable: true }]; state: { open: false, generation: 1, active-index: 0, active: { present: true, token: 11 }, selected: { present: true, token: 11 }, selection-availability: SelectionAvailability.available }; }
        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
required bool in
required-message string in
custom-validity string in-out
validity KitValidityState out
will-validate bool out
validation-message string out
theme KitTheme in
control-size ControlSize in
access FieldAccess in
rows [OptionRow] in
chips [ChipRow] in
state SelectState in
placeholder string in

Types et variantes

Type Déclaration
struct ChipRow token: int, label: string, removable: bool
enum CloseReason escape, outside-press, system-back, trigger-toggle, programmatic, host-closed
enum ControlSize compact, medium, comfortable
enum FieldAccess editable, read-only, disabled
struct KitValidityState valid: bool, value-missing: bool, type-mismatch: bool, pattern-mismatch: bool, too-long: bool, too-short: bool, range-underflow: bool, range-overflow: bool, step-mismatch: bool, bad-input: bool, custom-error: bool,
enum NavigationStep next, previous, first, last, page-next, page-previous
struct OptionRow token: int, generation: int, label: string, description: string, disabled: bool
enum SelectionAvailability absent, available, unavailable
struct SelectionRef present: bool, token: int
struct SelectState selection-availability: SelectionAvailability, selected: SelectionRef, active: SelectionRef, open: bool, active-index: int, generation: int

Signaux

Fonctions

Voir aussi