要素
Text
Text は段落を描画します。文字列を表示する要素であり、View の子に置いた文字列は何も描画されません。段落はネイティブでタイプセットされ(iOS と Mac Catalyst では CoreText、Android では StaticLayout)、エンジンスレッドで計測されるため、メインスレッドは描画だけを行います。Rust では text(content)、TSX では nsh から <Text> を使って作成します。
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 style={{ fontSize: 16, lineHeight: 24, color: "#1f2328" }}>
Read the <Text style={{ fontWeight: "700" }}>terms</Text> before you continue.
</Text>text(..) は文字列、SharedString、シグナル、またはクロージャ(text(move || format!("{} items", count.get())))を受け取ります。クロージャやシグナルを渡すと、それが読み取る値が変わったときにテキストがその場で更新されます。
#Spans
text の中に置いた text は、別のビューではなく同じ段落のスパンになります。スパンは段落のテキストスタイル(color、fontSize、fontWeight など)を継承し、自身で設定したものだけを上書きします。ブロック全体が 1 つのネイティブビューです。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 style={{ fontSize: 17 }}>
<Text style={{ fontWeight: "300" }}>Light </Text>
<Text style={{ fontWeight: "bold" }}>Bold </Text>
<Text style={{ fontStyle: "italic", color: "#8250df" }}>Italic</Text>
</Text>#スパンの背景
背景を持つスパンは、その文字のボックスを文字の背後に塗りつぶします。ボックスの高さはフォントの高さ(行の高さに関係なく、ベースラインより上のアセントと下のディセント)で、折り返した各行に対して、行ごとの部分がそれぞれスパンの角丸を持つ独立したボックスになります。内側のスパンは、独自の背景を設定しない限りこの背景を共有します。これは描画のみに作用します。テキストの配置は背景がない場合と同じで、選択可能なテキストでは、スパンの文字も他の文字と一緒にコピーされます。インラインコードはスパンです。
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.")<Text style={{ userSelect: "text", fontSize: 15, lineHeight: 22 }}>
{"Run "}
<Text style={{ fontFamily: "ui-monospace", fontSize: 13, backgroundColor: "#eff1f3", borderRadius: 4 }}>
{" cargo test "}
</Text>
{" before you commit."}
</Text>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 style={{ fontSize: 15 }} onPress={() => log("the sentence")}>
{"By continuing you agree to the "}
<Text style={{ color: "#0969da", textDecorationLine: "underline" }} onPress={() => open("terms")}>
Terms of service
</Text>
.
</Text>#インラインビューと画像
text の中に置かれたその他の要素(ビューや画像。スパンの内側でも同様)は、インラインアタッチメントになります。アタッチメントは自身のサイズでレイアウトされ、ベースライン上(または verticalAlign スタイルで指定した位置。CSS と同様に "text-top"、"text-bottom"、"middle"。行はそれを収めるために広がります)に配置され、単語とともに折り返されます。アタッチメントは自身への押下を受け取ります。
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)),
)<Text style={{ fontSize: 16, lineHeight: 26 }}>
{"Made with "}
<Image src={logo} style={{ width: 18, height: 18, borderRadius: 4 }} />
{" NSH. Status "}
<View style={{ backgroundColor: "#1a7f37", borderRadius: 9, paddingHorizontal: 7, height: 18, justifyContent: "center" }}>
<Text style={{ color: "#ffffff", fontSize: 11, fontWeight: "700" }}>LIVE</Text>
</View>
</Text>numberOfLines や省略記号によって切り取られたアタッチメントは非表示になります。選択可能なテキストをコピーする際、アタッチメントは含まれません。
#空白
JSX のテキストは React のルールに従います。テキストの両端を除いて各行はトリムされ、空白だけの行は削除され、残った行は 1 つのスペースで連結されます。1 行内のスペースは保持されます({a} {b} のスペースは保持されます)。スパンの先頭または末尾にスペースが必要な場合は、{" "} または文字列式を使ってください。Rust の文字列は書かれたとおりに描画されます。
#プロパティ
共通プロパティ(style は TextStyle 型)に加えて、次のものがあります。
| プロパティ | Rust | 型 | デフォルト | 説明 |
|---|---|---|---|---|
numberOfLines (style) | .number_of_lines(n), .truncate() | number | 0 | 表示する最大行数。0 はすべての行を表示します。 |
textOverflow (style) | .text_overflow(EllipsizeMode::Middle) | "ellipsis" | "clip" | "ellipsis-start" | "ellipsis-middle" | "ellipsis" | numberOfLines に収まらないテキストの切り取り方。末尾、先頭、または中間に省略記号を付けるか、クリップします。 |
allowFontScaling | .allow_font_scaling(..) | boolean | true | システムのテキストサイズ設定に従います。 |
maxFontSizeMultiplier | .max_font_size_multiplier(..) | number | 0 | システムスケールの上限。0 は上限なしです。 |
adjustsFontSizeToFit | .shrink_to_fit(min_scale) | boolean | false | テキストが行数(numberOfLines)とボックスに収まるまでフォントを縮小します。 |
minimumFontScale | .shrink_to_fit(min_scale) | number | adjustsFontSizeToFit が縮小する最大の度合い(0..1。最小は 0.1)。 | |
userSelect (style) | .select_text(), .select_none() | "auto" | "none" | "text" | "auto" | "text": ユーザーがテキストの範囲を選択できます。長押しで単語が選択され、ハンドルで範囲を広げ、編集メニューでコピーできます。"auto" は周囲のビューの設定を引き継ぎます。何も設定されていない場合、テキストは選択できません。ただしデスクトップブラウザでは、インタラクティブな要素の内側でない限り選択できます。"none" を指定すると、そこでも選択できなくなります。 |
selectionColor (style) | .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 は省略記号で切り取る 1 行になります。選択範囲のハイライトとハンドルにはスタイルキー selectionColor(selection:text-*)が使われ、デフォルトはプラットフォームのアクセントカラーです。
#テキストスタイル
TextStyle はビューのスタイルに次のものを追加します: color、fontSize、fontWeight("normal"、"bold"、または 100–900)、fontStyle、fontFamily、lineHeight、letterSpacing、textAlign、textAlignVertical、textTransform、textDecorationLine、textDecorationColor、textDecorationStyle、textDecorationThickness、textUnderlineOffset、textShadow(CSS の text-shadow)、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(..) など。スタイルリファレンスとテキストを参照してください。
フォントファミリーには、システムのファミリーか、アプリのフォントファイルを指定できます。
// 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))import { Text } from "nsh";
import Inter from "./fonts/Inter-Regular.ttf";
<Text style={{ fontFamily: Inter, fontSize: 17 }}>Inter</Text>;フォントは最初のフレームより前に登録されます。フォントを参照してください。
#イベント
| イベント | 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 メソッド
固有のものはありません。
#プラットフォームに関する注意
- 選択は 1 つのテキストブロック(1 つの text とそのスパン)の内側にとどまります。iOS では編集メニュー(コピー、すべてを選択、調べる、翻訳、共有)が、Android ではフローティングツールバー(コピー、すべて選択、共有)が表示されます。
- 実線の一重線以外のテキスト装飾(
double、dotted、dashed、wavy)は、iOS では NSH が描画します。 - iOS のテキストは、単色の背景と外側のシャドウを描画します。
nshのfontScale()(Rust ではfont_scale())は、現在のシステムのテキストスケールを返します。