Эта страница переведена машинным способом и может содержать ошибки. Читать оригинал на английском

Элементы

Text

Text рисует абзац. Это элемент, который показывает строки: строка-потомок View ничего не рисует. Абзац верстается нативно (CoreText на iOS и Mac Catalyst, StaticLayout на Android) и измеряется в потоке движка, поэтому основной поток его только рисует. В Rust его создаёт text(content), в TSX — <Text> из 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(..) принимает строку, SharedString, сигнал или замыкание (text(move || format!("{} items", count.get()))): замыкание или сигнал обновляет текст на месте, когда меняется то, что они читают.

#Спаны

Text внутри text — это спан того же абзаца, а не отдельное представление. Спаны наследуют стили текста абзаца (color, fontSize, fontWeight, …) и переопределяют те, которые задают сами. Весь блок — одно нативное представление. В Rust text(..) показывает свой контент, а затем потомков: .child(text(..)) добавляет спан, .child("…") — обычный фрагмент текста.

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)))

#Фоны спанов

Спан с фоном заливает область своих символов позади них: высотой со свой шрифт (подъём над базовой линией и спуск под ней, независимо от высоты строки) и на каждой строке, на которую он переносится, часть спана на этой строке образует отдельную область со скруглением спана. Вложенные в него спаны используют его фон, если не задают собственный. Это только отрисовка: текст раскладывается так же, как без фона, а выделяемый текст копирует символы спана вместе с остальными. Строчный код — это спан:

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.")

Классы Tailwind работают так же: <Text class="bg-gray-100 rounded">. Отступы и рамки — стили блока и спаны не затрагивают; пробелы внутри спана оставляют области место по бокам.

#Ссылки

Спан с обработчиком нажатия — это ссылка: нажатие на его символы попадает в спан, нажатие в другом месте — в абзац.

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(".")

#Встроенные представления и изображения

Любой другой элемент внутри text (представление, изображение, в том числе внутри спанов) — это встроенное вложение: оно раскладывается в своём размере, располагается на базовой линии (или там, куда его ставит стиль verticalAlign: "text-top", "text-bottom", "middle", как в CSS; строка увеличивается, чтобы его вместить) и переносится вместе со словами. Оно принимает собственные нажатия.

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)),
    )

Вложения, обрезанные numberOfLines или многоточием, скрываются. При копировании выделяемого текста они не попадают в результат.

#Пробелы

Текст в JSX следует правилам React: строки обрезаются, кроме краёв текста, строки только из пробелов отбрасываются, а оставшиеся строки соединяются одним пробелом. Пробелы внутри одной строки сохраняются ({a} {b} сохраняет свой пробел). Пишите {" "} или строковое выражение, когда спану нужен пробел в начале или в конце. Строки Rust рисуются как написаны.

#Свойства

Помимо общих свойств (с style типа TextStyle):

СвойствоRustТипПо умолчаниюОписание
numberOfLines (стиль).number_of_lines(n), .truncate()number0Максимальное число показываемых строк; 0 показывает все.
textOverflow (стиль).text_overflow(EllipsizeMode::Middle)"ellipsis" | "clip" | "ellipsis-start" | "ellipsis-middle""ellipsis"Как обрезается текст, не помещающийся в numberOfLines: многоточие в конце, в начале или в середине либо простая обрезка.
allowFontScaling.allow_font_scaling(..)booleantrueСледовать системной настройке размера текста.
maxFontSizeMultiplier.max_font_size_multiplier(..)number0Верхняя граница системного масштаба; 0 означает отсутствие границы.
adjustsFontSizeToFit.shrink_to_fit(min_scale)booleanfalseУменьшать шрифт, пока текст не поместится в свои строки (numberOfLines) и в свой блок.
minimumFontScale.shrink_to_fit(min_scale)numberНасколько максимально adjustsFontSizeToFit уменьшает шрифт (0..1; не менее 0.1).
userSelect (стиль).select_text(), .select_none()"auto" | "none" | "text""auto""text": пользователь может выделять фрагменты текста: долгое нажатие выделяет слово, маркеры расширяют выделение, меню правки копирует его. "auto" берёт настройку окружающих представлений; если она нигде не задана, текст не выделяется, кроме настольных браузеров, где он выделяется, если не находится внутри интерактивного элемента; "none" запрещает выделение и там.
selectionColor (стиль).selection_color(..)ColorValueакцентный цвет платформыПодсветка и маркеры выделения.
onTextLayout(e: { lines: TextLayoutLine[] }) => voidРазложенные строки после каждой раскладки.

Классы Tailwind select-text, select-none и select-auto задают userSelect, line-clamp-* задаёт numberOfLines, text-ellipsis и text-clip задают textOverflow, а truncate — это одна строка, обрезанная многоточием. Подсветка и маркеры выделения берут ключ стиля selectionColor (selection:text-*), по умолчанию — акцентный цвет платформы.

#Стили текста

TextStyle дополняет стили представления: color, fontSize, fontWeight ("normal", "bold" или 100–900), fontStyle, fontFamily, lineHeight, letterSpacing, textAlign, textAlignVertical, textTransform, textDecorationLine, textDecorationColor, textDecorationStyle, textDecorationThickness, textUnderlineOffset, textShadow (аналог text-shadow в CSS), fontVariant, writingDirection, numberOfLines и textOverflow. В Rust это методы Styled: .text_color(..), .text_size(..) (или .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(..) и остальные. См. справочник по стилям и text.

Семейство шрифтов — это системное семейство или файл шрифта приложения:

// 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))

Шрифт регистрируется до первого кадра. См. шрифты.

#События

СобытиеRustДанныеКогда
onTextLayout{ lines: TextLayoutLine[] }После каждой раскладки абзаца.
onPress, onLongPress, ….on_press, .on_long_press, …PressEvent (в Rust отсутствуют)Как у view; у спана — нажатия на его символы.
onLayout.on_layout{ bounds }Кадр блока изменился.

TextLayoutLine — это { text, x, y, width, height, ascender, descender, capHeight, xHeight } в области контента текста:

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

Rust измеряет строку до раскладки с помощью measure_text(..).

#Методы ref

Собственных нет.

#Примечания по платформам

  • Выделение остаётся внутри одного текстового блока (один text и его спаны). На iOS показывается меню правки (Copy, Select All, Look Up, Translate, Share), на Android — плавающая панель (Copy, Select all, Share).
  • Оформление текста, отличное от сплошной одинарной линии (double, dotted, dashed, wavy), на iOS рисуется силами NSH.
  • Тексты на iOS рисуют сплошные фоны и внешние тени.
  • fontScale() из nsh (font_scale() в Rust) сообщает текущий системный масштаб текста.