Cette page a été traduite automatiquement et peut contenir des erreurs. Lire l'original en anglais

Éléments

Text

Text dessine un paragraphe. C’est l’élément qui affiche des chaînes : une chaîne enfant d’un View ne dessine rien. Le paragraphe est composé nativement (CoreText sur iOS et Mac Catalyst, StaticLayout sur Android) et mesuré sur le thread du moteur, de sorte que le thread principal ne fait que le dessiner. Rust en crée un avec text(content), TSX avec <Text> depuis nsh.

el-text.tsx
el-text.tsxLive
import { mount, signal, Text, View } from "nsh";
import { button, buttonText, focusRing, ui } from "./palette";

const LONG =
  "Text is typeset natively (CoreText on iOS, StaticLayout on Android) and measured on the engine thread, so the main thread only draws it.";

function App() {
  const tapped = signal("nothing yet");
  const lines = signal(2);
  const link = () => ({ color: ui().accent, textDecorationLine: "underline" }) as const;
  return (
    <View style={{ flex: 1, backgroundColor: ui().page }}>
      <View style={{ flex: 1, width: "100%", maxWidth: 560, alignSelf: "center", padding: 20, paddingTop: 24, gap: 14 }}>
        <Text style={{ fontSize: 22, fontWeight: "700", color: ui().text }}>text</Text>

        <Text style={{ fontSize: 16, lineHeight: 24, color: ui().text }}>
          {"Nested text elements are spans: "}
          <Text style={{ fontWeight: "700" }}>bold</Text>
          {", "}
          <Text style={{ fontStyle: "italic", color: ui().violet }}>italic</Text>
          {", and "}
          <Text style={link()} onPress={() => tapped.set("the first link")} hoverStyle={{ opacity: 0.88 }} focusStyle={focusRing()} activeStyle={{ opacity: 0.7 }}>
            links
          </Text>
          {" that take presses of "}
          <Text style={link()} onPress={() => tapped.set("the second link")} hoverStyle={{ opacity: 0.88 }} focusStyle={focusRing()} activeStyle={{ opacity: 0.7 }}>
            their own
          </Text>
          .
        </Text>
        <Text style={{ fontSize: 13, color: ui().muted }}>Pressed: {tapped()}</Text>

        <View style={{ height: 1, backgroundColor: ui().line }} />

        <Text style={{ numberOfLines: lines(), fontSize: 15, color: ui().text }}>
          {LONG}
        </Text>
        <View style={{ flexDirection: "row", gap: 8 }}>
          <View
            onPress={() => lines.set(lines() === 2 ? 0 : 2)}
            {...button()}
          >
            <Text style={buttonText()}>{lines() === 2 ? "Show all" : "numberOfLines 2"}</Text>
          </View>
        </View>

        <Text style={{ numberOfLines: 1, textOverflow: "ellipsis-middle", fontSize: 15, color: ui().muted }}>
          {LONG}
        </Text>

        <Text style={{ fontSize: 17, color: ui().text }}>
          <Text style={{ textDecorationLine: "line-through", textDecorationColor: ui().down }}>struck</Text>{" "}
          <Text style={{ textTransform: "uppercase", letterSpacing: 2 }}>spaced</Text>{" "}
          <Text style={{ fontVariant: ["tabular-nums"] }}>0123456789</Text>
        </Text>

        <Text
          style={{
            fontSize: 28,
            fontWeight: "800",
            color: ui().onAccent,
            backgroundColor: "#c8f26d",
            paddingHorizontal: 10,
            borderRadius: 8,
            textShadow: "2px 2px 4px rgba(255,255,255,0.55)",
          }}
        >
          Text shadow
        </Text>
      </View>
    </View>
  );
}

mount(App);
use nsh_core::prelude::*;

text("Read the ")
    .text_base()
    .line_height(px(24.0))
    .text_color(rgb(0x1f2328))
    .child(text("terms").font_weight(FontWeight::BOLD))
    .child(" before you continue.")

text(..) accepte une chaîne, un SharedString, un signal ou une closure (text(move || format!("{} items", count.get()))) : une closure ou un signal met à jour le texte sur place lorsque ce qu’il lit change.

#Spans

Un texte à l’intérieur d’un texte est un span du même paragraphe, et non une vue distincte. Les spans héritent des styles de texte du paragraphe (color, fontSize, fontWeight, …) et remplacent ceux qu’ils définissent. Le bloc entier est une seule vue native. En Rust, text(..) affiche son contenu puis ses enfants : .child(text(..)) ajoute un span, .child("…") un segment de texte brut.

text("")
    .text_size(px(17.0))
    .child(text("Light ").font_weight(FontWeight::LIGHT))
    .child(text("Bold ").font_weight(FontWeight::BOLD))
    .child(text("Italic").italic().text_color(rgb(0x8250df)))

#Span backgrounds

Un span avec un arrière-plan remplit la boîte de ses caractères, derrière eux : aussi haute que sa police (l’ascendante au-dessus de la ligne de base et la descendante en dessous, quelle que soit la hauteur de ligne), et sur chaque ligne où il passe à la ligne, la partie de chaque ligne formant une boîte distincte avec le rayon de coin du span. Les spans qu’il contient partagent son arrière-plan, sauf s’ils définissent le leur. Il s’agit uniquement de dessin : le texte est mis en page comme s’il n’y en avait pas, et un texte sélectionnable copie les caractères du span avec le reste. Le code en ligne est un span :

text("Run ")
    .text_size(px(15.0))
    .line_height(px(22.0))
    .select_text()
    .child(
        text(" cargo test ")
            .font_family("ui-monospace")
            .text_size(px(13.0))
            .bg(rgb(0xeff1f3))
            .rounded(px(4.0)),
    )
    .child(" before you commit.")

Les classes Tailwind fonctionnent de la même manière : <Text class="bg-gray-100 rounded">. Le padding et les bordures sont des styles de boîte et n’affectent pas les spans ; des espaces à l’intérieur du span donnent de la place à la boîte sur ses côtés.

Un span avec un gestionnaire de pression est un lien : une pression sur ses caractères va au span, une pression ailleurs au paragraphe.

text("By continuing you agree to the ")
    .text_size(px(15.0))
    .on_press(move || log("the sentence"))
    .child(
        text("Terms of service")
            .text_color(rgb(0x0969da))
            .underline()
            .on_press(move || open("terms")),
    )
    .child(".")

#Inline views and images

Tout autre élément à l’intérieur d’un texte (une vue, une image, même à l’intérieur de spans) est une pièce jointe en ligne : elle est mise en page à sa propre taille, se place sur la ligne de base (ou là où son style verticalAlign la place : "text-top", "text-bottom", "middle", comme en CSS ; la ligne s’agrandit pour la contenir) et passe à la ligne avec les mots. Elle reçoit ses propres pressions.

text("Made with ")
    .text_base()
    .line_height(px(26.0))
    .child(image(LOGO).size(px(18.0)).rounded(px(4.0)))
    .child(" NSH. Status ")
    .child(
        view()
            .bg(rgb(0x1a7f37))
            .rounded(px(9.0))
            .px(px(7.0))
            .h(px(18.0))
            .justify_center()
            .child(text("LIVE").text_color(white()).text_size(px(11.0)).font_weight(FontWeight::BOLD)),
    )

Les pièces jointes coupées par numberOfLines ou par une ellipse sont masquées. La copie d’un texte sélectionnable les omet.

#Whitespace

Le texte JSX suit les règles de React : les lignes sont rognées sauf aux extrémités du texte, les lignes ne contenant que des espaces sont supprimées et les lignes restantes sont jointes par une seule espace. Les espaces sur une même ligne sont conservées ({a} {b} garde son espace). Écrivez {" "} ou une expression de chaîne lorsqu’un span a besoin d’une espace au début ou à la fin. Les chaînes Rust sont dessinées telles qu’elles sont écrites.

#Props

En plus des props communes (avec style typé comme TextStyle) :

PropRustTypeDéfautDescription
numberOfLines (style).number_of_lines(n), .truncate()number0Le nombre maximal de lignes affichées ; 0 les affiche toutes.
textOverflow (style).text_overflow(EllipsizeMode::Middle)"ellipsis" | "clip" | "ellipsis-start" | "ellipsis-middle""ellipsis"La façon dont le texte qui ne tient pas dans numberOfLines est coupé : une ellipse à sa fin, à son début ou en son milieu, ou un rognage.
allowFontScaling.allow_font_scaling(..)booleantrueSuit le réglage de taille de texte du système.
maxFontSizeMultiplier.max_font_size_multiplier(..)number0La borne supérieure de l’échelle du système ; 0 signifie aucune.
adjustsFontSizeToFit.shrink_to_fit(min_scale)booleanfalseRéduit la police jusqu’à ce que le texte tienne dans ses lignes (numberOfLines) et dans sa boîte.
minimumFontScale.shrink_to_fit(min_scale)numberJusqu’où adjustsFontSizeToFit peut réduire au maximum (0..1 ; au moins 0.1).
userSelect (style).select_text(), .select_none()"auto" | "none" | "text""auto""text" : l’utilisateur peut sélectionner des plages du texte : un appui long sélectionne un mot, des poignées l’étendent, le menu d’édition le copie. "auto" reprend le réglage des vues autour du texte ; si aucun n’est défini, le texte n’est pas sélectionnable, sauf dans les navigateurs de bureau, où il l’est à moins qu’il ne se trouve dans un élément interactif ; "none" le rend là aussi non sélectionnable.
selectionColor (style).selection_color(..)ColorValuela couleur d’accent de la plateformeLa surbrillance et les poignées de la sélection.
onTextLayout(e: { lines: TextLayoutLine[] }) => voidLes lignes mises en page après chaque mise en page.

Les classes Tailwind select-text, select-none et select-auto définissent userSelect, line-clamp-* définit numberOfLines, text-ellipsis et text-clip définissent textOverflow, et truncate donne une seule ligne coupée par une ellipse. La surbrillance et les poignées de la sélection utilisent la clé de style selectionColor (selection:text-*), la couleur d’accent de la plateforme par défaut.

#Text styles

TextStyle s’ajoute aux styles de vue : color, fontSize, fontWeight ("normal", "bold" ou 100–900), fontStyle, fontFamily, lineHeight, letterSpacing, textAlign, textAlignVertical, textTransform, textDecorationLine, textDecorationColor, textDecorationStyle, textDecorationThickness, textUnderlineOffset, textShadow (le text-shadow de CSS), fontVariant, writingDirection, numberOfLines et textOverflow. En Rust, ce sont des méthodes de Styled : .text_color(..), .text_size(..) (ou .text_sm(), .text_lg(), …), .font_weight(FontWeight::SEMIBOLD), .italic(), .font_family(..), .line_height(..), .letter_spacing(..), .text_center(), .uppercase(), .underline(), .line_through(), .tabular_nums(), .number_of_lines(..) et les autres. Voir la référence des styles et text.

La famille de police est une famille système ou un fichier de police de l’application :

// assets/fonts/Inter-Regular.ttf, shipped by the registry (`registry.assets(assets!())`).
const INTER: FontAsset = asset!("fonts/Inter-Regular.ttf");

text("Inter").font_family(INTER.family).text_size(px(17.0))

La police est enregistrée avant la première frame. Voir fonts.

#Events

EventRustPayloadQuand
onTextLayout{ lines: TextLayoutLine[] }Après chaque mise en page du paragraphe.
onPress, onLongPress, ….on_press, .on_long_press, …PressEvent (aucun en Rust)Comme sur view ; sur un span, les pressions sur ses caractères.
onLayout.on_layout{ bounds }Le cadre du bloc a changé.

TextLayoutLine vaut { text, x, y, width, height, ascender, descender, capHeight, xHeight }, dans la boîte de contenu du texte :

const lines = signal(0);
<Text onTextLayout={(e) => lines.set(e.lines.length)}>{article}</Text>;

Rust mesure une chaîne avant sa mise en page avec measure_text(..).

#Ref methods

Aucune qui lui soit propre.

#Platform notes

  • La sélection reste à l’intérieur d’un seul bloc de texte (un texte et ses spans). iOS affiche son menu d’édition (Copy, Select All, Look Up, Translate, Share), Android une barre d’outils flottante (Copy, Select all, Share).
  • Les décorations de texte autres qu’une ligne simple continue (double, dotted, dashed, wavy) sont dessinées par NSH sur iOS.
  • Les textes iOS dessinent des arrière-plans pleins et des ombres extérieures.
  • fontScale() de nsh (font_scale() en Rust) renvoie l’échelle de texte actuelle du système.