Vue de texte virtualisée pour Slint
KitVirtualTextView · Slint Kit 0.3.1
Vue de document Slint avec sélection et édition native d’une ligne.

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.
Sélectionnez une ligne, modifiez-la dans le champ natif, puis cliquez sur Save line. Rust met cette ligne à jour sans changer sa clé.
ui/app.slint
export { KitFormValidation, HtmlInputKind, NumberInputFormat, KitInputConstraints, KitValidationResult, KitValidityState } from "@kit/kit.slint";
// SPDX-License-Identifier: GPL-3.0-or-later
import { KitVirtualTextView, KitThemes, TextLine, TextViewAccess, NavigationStep } from "@kit/kit.slint";
export { TextLine, NavigationStep }
export component App inherits Window {
title: "Slint virtual text view";
preferred-width: 640px; preferred-height: 340px;
max-width: 100000px; max-height: 100000px;
background: KitThemes.light.canvas;
in property <[TextLine]> lines;
in property <int> active-index: -1;
in-out property <string> draft;
in property <string> status: "Select a line to edit";
in property <string> error;
callback choose(int, int);
callback move(NavigationStep);
callback save(int, int, string);
callback copied(int, int);
VerticalLayout {
padding: 24px; spacing: 16px;
document := KitVirtualTextView { height: 240px; lines: root.lines; generation: 1;
active-index: root.active-index; access: TextViewAccess.edit-line;
line-draft <=> root.draft; error: root.error;
select-line(token, generation) => { root.choose(token, generation); }
navigate(step) => { root.move(step); }
commit-line(token, generation, text) => { root.save(token, generation, text); }
copy-requested(token, generation) => { root.copied(token, generation); }
}
Text { text: root.status; color: KitThemes.light.text-primary; }
}
}
src/main.rs
// SPDX-License-Identifier: GPL-3.0-or-later
use slint::{ComponentHandle, ModelRc, VecModel};
use std::{cell::RefCell, rc::Rc};
mod ui {
slint::include_modules!();
}
fn next_index(current: Option<usize>, length: usize, step: ui::NavigationStep) -> usize {
use ui::NavigationStep::*;
let current = current.unwrap_or(0);
match step {
First => 0,
Last => length.saturating_sub(1),
Previous | PagePrevious => current.saturating_sub(1),
Next | PageNext => (current + 1).min(length.saturating_sub(1)),
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum LineKey {
Heading,
Body,
Closing,
}
impl LineKey {
fn token(self) -> i32 {
match self {
Self::Heading => 11,
Self::Body => 23,
Self::Closing => 37,
}
}
}
struct Line {
key: LineKey,
text: String,
}
struct State {
lines: Vec<Line>,
selected: Option<LineKey>,
}
fn select(state: &State, app: &ui::App) {
let index = state
.lines
.iter()
.position(|line| Some(line.key) == state.selected);
let draft = index.map_or("", |i| state.lines[i].text.as_str());
app.set_active_index(index.map_or(-1, |i| i as i32));
app.set_draft(draft.into());
app.set_error("".into());
}
fn publish(state: &RefCell<State>, app: &ui::App) {
let state = state.borrow();
let lines = state
.lines
.iter()
.enumerate()
.map(|(index, line)| ui::TextLine {
token: line.key.token(),
number: index as i32 + 1,
text: line.text.as_str().into(),
selected: state.selected == Some(line.key),
})
.collect::<Vec<_>>();
drop(state);
app.set_lines(ModelRc::new(VecModel::from(lines)));
}
fn main() -> Result<(), slint::PlatformError> {
let app = ui::App::new()?;
slint_kit::init_form_validation!(ui, app);
let state = Rc::new(RefCell::new(State {
lines: vec![
Line {
key: LineKey::Heading,
text: "# Project notes".into(),
},
Line {
key: LineKey::Body,
text: "Select and edit this line.".into(),
},
Line {
key: LineKey::Closing,
text: "Rust owns the saved text.".into(),
},
],
selected: None,
}));
publish(&state, &app);
let (model, weak) = (state.clone(), app.as_weak());
app.on_choose(move |token, generation| {
if generation != 1 {
return;
}
{
let mut state = model.borrow_mut();
let Some(key) = state
.lines
.iter()
.find(|line| line.key.token() == token)
.map(|line| line.key)
else {
return;
};
state.selected = Some(key);
}
if let Some(app) = weak.upgrade() {
select(&model.borrow(), &app);
publish(&model, &app);
}
});
let (model, weak) = (state.clone(), app.as_weak());
app.on_move(move |step| {
{
let mut state = model.borrow_mut();
let current = state
.lines
.iter()
.position(|line| Some(line.key) == state.selected);
let index = next_index(current, state.lines.len(), step);
state.selected = Some(state.lines[index].key);
}
if let Some(app) = weak.upgrade() {
select(&model.borrow(), &app);
publish(&model, &app);
}
});
let (model, weak) = (state.clone(), app.as_weak());
app.on_save(move |token, generation, text| {
if generation != 1 {
return;
}
let Some(app) = weak.upgrade() else {
return;
};
if text.contains(['\n', '\r']) {
app.set_error("Enter one line only".into());
return;
}
{
let mut state = model.borrow_mut();
if state.selected.map(LineKey::token) != Some(token) {
return;
}
let Some(line) = state
.lines
.iter_mut()
.find(|line| line.key.token() == token)
else {
return;
};
line.text = text.to_string();
}
app.set_error("".into());
app.set_status("Line saved in Rust".into());
publish(&model, &app);
});
let weak = app.as_weak();
app.on_copied(move |_token, generation| {
if generation == 1 {
if let Some(app) = weak.upgrade() {
app.set_status("Selected line copied".into());
}
}
});
app.run()
}
Cargo.toml et build.rs
Cargo.toml
[package]
name = "slint-kit-virtual-text-view-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
Fournissez lines, generation et active-index. Choisissez access: TextViewAccess.edit-line, liez line-draft et enregistrez le texte accepté dans Rust via commit-line(token, generation, text).
Traitez select-line pour charger le brouillon sélectionné et navigate pour changer de ligne. copy-requested signale une copie. Utilisez KitTextArea pour éditer un tampon multiligne complet.
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 { KitTheme, KitThemes, KitVirtualTextView } from "@kit/kit.slint";
export component Example inherits Window {
title: "KitVirtualTextView — exemple";
preferred-width: 620px;
preferred-height: 360px;
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: "Vue de texte virtualisée"; color: root.theme.text-primary; font-size: 20px * root.theme.text-scale; font-weight: 600; }
KitVirtualTextView { label: "Document texte"; theme: root.theme; height: 230px; generation: 1; lines: [{ token: 101, number: 1, text: "# Notes de terrain", selected: false }, { token: 205, number: 2, text: "Un espace partagé pour les observations.", selected: true }, { token: 309, number: 3, text: "Préservez les identités lorsque les lignes changent de position.", selected: false }, { token: 413, number: 4, text: "Utilisez l’édition native pour les lignes sélectionnées.", selected: false }]; }
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 |
lines |
[TextLine] |
in |
generation |
int |
in |
active-index |
int |
in |
access |
TextViewAccess |
in |
line-draft |
string |
in-out |
error |
string |
in |
label |
string |
in |
scroll-y |
length |
out |
Types et variantes
| Type | Déclaration |
|---|---|
enum NavigationStep |
next, previous, first, last, page-next, page-previous |
struct TextLine |
token: int, number: int, text: string, selected: bool |
enum TextViewAccess |
read-only, edit-line |
Signaux
navigate(NavigationStep)select-line(int,int)commit-line(int,int,string)copy-requested(int,int)
Fonctions
take-focus()