Diese Seite wurde maschinell übersetzt und kann Fehler enthalten. Das englische Original lesen

Elemente

View

View ist der Baustein jedes Screens: ein nativer Container (eine UIView auf iOS und Mac Catalyst, eine ViewGroup auf Android), der seine Kinder mit Flexbox oder Grid anordnet, einen Hintergrund, Rahmen, Schatten und Verläufe zeichnet und Presses verarbeitet. Buttons, Karten, Zeilen und Screens sind alle Views. Rust erzeugt eine View mit view(), TSX mit <View> aus nsh.

el-view.tsx
el-view.tsxLive
import { mount, signal, Text, View } from "nsh";
import { button, buttonText, desktop, focusRing, Inspector, Panel, Stage, ui, wide } from "./palette";

function App() {
  const presses = signal(0);
  const size = signal("");
  const views = () => (
    <>
      <View
        onPress={() => presses.update((n) => n + 1)}
        {...button("accent", { align: "center" })}
      >
        <Text style={buttonText("accent")}>activeStyle opacity · pressed {presses()}×</Text>
      </View>

      <View
        onPress={() => presses.update((n) => n + 1)}
        activeStyle={{ backgroundColor: ui().border }}
        style={{ ...ui().card, backgroundImage: "none", borderRadius: 16, padding: 16 }}
        hoverStyle={{ opacity: 0.88 }}
        focusStyle={focusRing()}
      >
        <Text style={{ color: ui().text, fontSize: wide() ? 14 : 16 }}>activeStyle background ({desktop ? "click" : "tap"} and hold)</Text>
      </View>

      <View class="flex-row gap-3">
        <View class="flex-1 h-20 rounded-xl bg-[#ff6170] hover:bg-[#ff7d89] active:bg-[#d94d5b] items-center justify-center" focusStyle={focusRing()} onPress={() => {}}>
          <Text class="text-[#13160c] font-semibold">active:</Text>
        </View>
        <View class="flex-1 h-20 rounded-xl bg-[#45d49a] items-center justify-center shadow-lg">
          <Text class="text-[#0c0d0f] font-semibold">shadow-lg</Text>
        </View>
        <View
          class="flex-1 h-20 rounded-xl items-center justify-center"
          style={{ backgroundImage: `linear-gradient(135deg, ${ui().violet}, ${ui().info})` }}
        >
          <Text class="font-semibold" style={{ color: ui().page }}>gradient</Text>
        </View>
      </View>
    </>
  );
  const measured = (height?: number) => (
    <View
      onLayout={({ bounds }) => size.set(`${Math.round(bounds.size.width)} × ${Math.round(bounds.size.height)} pt`)}
      style={{
        flex: height === undefined ? 1 : undefined,
        height,
        borderRadius: 12,
        borderWidth: 2,
        borderStyle: "dashed",
        borderColor: ui().faint,
        alignItems: "center",
        justifyContent: "center",
      }}
    >
      <Text style={{ color: ui().muted }}>onLayout: {size()}</Text>
    </View>
  );

  return (
    <View style={{ flex: 1, backgroundColor: ui().page }}>
      {() =>
        wide() ? (
          <View style={{ flex: 1, padding: 20, paddingTop: 24, gap: 14 }}>
            <Text style={{ fontSize: 22, fontWeight: "700", color: ui().text }}>view</Text>
            <Text style={{ color: ui().muted }}>Click and hold the views; resize the window to see onLayout</Text>
            <View style={{ flex: 1, flexDirection: "row", gap: 16 }}>
              <Stage width={480} gap={16}>
                {views()}
                {measured(120)}
              </Stage>
              <Inspector>
                <Panel title="onPress">
                  <Text style={{ fontSize: 13, color: ui().text, fontVariant: ["tabular-nums"] }}>pressed {presses()}×</Text>
                </Panel>
                <Panel title="onLayout" grow>
                  <Text style={{ fontSize: 13, color: ui().text, fontVariant: ["tabular-nums"] }}>{size()}</Text>
                </Panel>
              </Inspector>
            </View>
          </View>
        ) : (
          <View style={{ flex: 1, width: "100%", maxWidth: 640, alignSelf: "center", padding: 20, paddingTop: 24, gap: 16 }}>
            <Text style={{ fontSize: 22, fontWeight: "700", color: ui().text }}>view</Text>
            {views()}
            {measured()}
          </View>
        )
      }
    </View>
  );
}

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

fn app() -> impl IntoElement {
    let liked = signal(false);
    view()
        .flex_1()
        .p_6()
        .gap_4()
        .bg(rgb(0xf6f8fa))
        .child(
            view()
                .flex_row()
                .items_center()
                .gap_3()
                .p_4()
                .rounded(px(16.0))
                .bg(white())
                .shadow_md()
                .child(view().size(px(40.0)).rounded_full().bg(rgb(0x8250df)))
                .child(text("A card").flex_1().text_base())
                .child(
                    view()
                        .hit_slop(EdgeInsets::all(px(8.0)))
                        .active(|s| s.opacity(0.5))
                        .on_press(move || liked.update(|l| *l = !*l))
                        .child(text(move || if liked.get() { "♥" } else { "♡" }).text_size(px(22.0))),
                ),
        )
}

#Layout

Eine View ordnet ihre Kinder mit Flexbox an, mit diesen Standardwerten: flexDirection: "column", flexShrink: 0, alignContent: "flex-start", position: "relative". display: "grid" (.grid() in Rust) schaltet auf CSS Grid um, und display: "block" auf Block-Layout. Größen sind in Punkten angegeben. Siehe Layout und die Style-Referenz.

display: "flex" folgt CSS: In einem Style, als die Klasse flex und als Rusts .flex() ist es eine Zeile, sofern keine Richtung gesetzt ist (flexDirection: "column", flex-col, .flex_col()). Eine View, die kein Display setzt, stapelt ihre Kinder in einer Spalte.

#Props

View hat eine eigene Prop, selectable (.selectable(true) in Rust): Die Texte innerhalb der View werden gemeinsam als eine einzige Auswahl markiert (Textauswahl). Die übrigen sind die allgemeinen Props: ihre Styles (style, class, classList; die Styled-Methoden in Rust), disabled, die Press-Props (onPress, onLongPress, onPressDown, onPressUp, delayLongPress, hitSlop), die Zustands-Styles (activeStyle, hoverStyle, focusStyle, disabledStyle, checkedStyle), onMouseEnter / onMouseMove / onMouseLeave, onContextMenu, onFileDragEnter / onFileDragLeave / onFileDrop, focusable, onLayout, ref, nativeID, testID, die Gesten-Props (pan, pinch, rotation und ihre Events), die Touch- und Responder-Props, die Accessibility-Props und ios / android / desktop.

#Auswählbare Views

Mit selectable verläuft eine Auswahl in Lesereihenfolge über die Text-Elemente und TextViews innerhalb der View; Texte mit selectable={false} bleiben außen vor, und eine auswählbare View innerhalb einer anderen ist Teil der äußeren. Siehe Textauswahl für die Gesten auf jeder Plattform.

<View selectable class="gap-3 p-4">
  <Text>One selection runs from here…</Text>
  <Text>…to here.</Text>
</View>

#Press-Feedback

Eine drückbare View ist jede View mit einem Press-Handler. Ihr Feedback gehört der App: ein Style während des Drückens, der nativ angewendet wird, solange der Finger aufliegt, ohne Umweg über den Code der App. hoverStyle, focusStyle und disabledStyle leisten dasselbe für die anderen Zustände:

// Fade the whole view.
view().on_press(open).active(|s| s.opacity(0.6))

// Show a background; group_active styles the children.
view()
    .group()
    .on_press(open)
    .active(|s| s.bg(rgb(0xe5e7eb)))
    .child(text("Open").group_active(|s| s.opacity(0.7)))

// Change any style while pressed.
view().on_press(open).bg(rgb(0x2563eb)).active(|s| s.bg(rgb(0x1e40af)).scale(0.95))

Auf Android fügt android={{ ripple: { color: "#0000001f" } }} (.ripple(Ripple { .. }) in Rust) den Material- Ripple hinzu (borderless, radius und foreground passen ihn an).

disabled entfernt die Press-Listener von der nativen View (kein Press, kein Feedback) und wendet die Disabled-Styles an:

view()
    .on_press(submit)
    .disabled(move || !valid.get())
    .bg(rgb(0x16a34a))
    .disabled_style(|s| s.opacity(0.4))

#Events

Event Rust Payload Wann
onPress .on_press PressEvent (in Rust keiner) Ein Tap endete innerhalb der View.
onLongPress .on_long_press PressEvent (in Rust keiner) Der Finger blieb für delayLongPress (500 ms) aufgelegt.
onPressDown / onPressUp .on_press_down / .on_press_up PressEvent (in Rust keiner) Ein Finger wurde aufgelegt / abgehoben oder hat die View verlassen.
onMouseEnter / onMouseLeave .on_mouse_enter / .on_mouse_leave MouseEvent Die Maus kam über die View (oder eine View darin) / verließ sie oder das Fenster (Hover); bei onMouseLeave ist es die Position, an der sie zuletzt war.
onMouseMove .on_mouse_move MouseEvent Die Maus bewegte sich über der View (oder einer View darin), mit oder ohne gedrückte Taste, oder eine Modifikatortaste wurde gedrückt oder losgelassen, während sie dort ohne gedrückte Taste ruht: position, pagePosition (Points), modifiers und pressedButton ("left", "right", "middle", "back", "forward" oder null); höchstens einmal pro Frame.
onContextMenu .on_context_menu ContextMenuEvent Die sekundäre Taste wurde über der View gedrückt (Kontextmenüs).
onFileDragEnter / onFileDragLeave .on_file_drag_enter / .on_file_drag_leave keiner Ein Drag lokaler Dateien betrat / verließ das aktive Desktop-Ziel.
onFileDrop .on_file_drop { uris: string[] } (FileDropEvent) Lokale Dateien oder Verzeichnisse wurden auf dem Desktop-Ziel abgelegt.
onLayout .on_layout { bounds } (Bounds<Points>) Der Frame relativ zum Parent hat sich geändert.
onPanStart / onPanEnd .on_pan_start / .on_pan_end PanEvent Ein mit pan gebundener Drag wurde gestartet / beendet; cancelled ist true, wenn das System ihn abbricht.
onPinchStart / onPinchEnd .on_pinch_start / .on_pinch_end { scale, velocity } Ein mit pinch gebundener Pinch wurde gestartet / beendet.
onRotationStart / onRotationEnd .on_rotation_start / .on_rotation_end { rotation, velocity } Eine mit rotation gebundene Rotation wurde gestartet / beendet.
onAccessibilityAction .on_accessibility_action { actionName } Eine benutzerdefinierte Accessibility-Aktion wurde ausgeführt.

Touch- und Responder-Events: siehe allgemeine Props.

#Datei-Drops auf dem Desktop

Auf AppKit, GTK, Win32 und GPUI empfängt eine View mit onFileDrop lokale Dateien und Verzeichnisse, die aus einer anderen Anwendung hineingezogen werden. onFileDragEnter und onFileDragLeave verfolgen das aktive Ziel; onFileDrop empfängt { uris: string[] } in der nativen Reihenfolge der Dateiliste. Jeder Eintrag ist eine absolute file:-URL mit maskierten Leerzeichen und Nicht-ASCII-Zeichen. Parsen Sie die URL, um einen Dateisystempfad zu erhalten, anstatt das Präfix von Hand zu entfernen.

let hovering = signal(false);
let files = signal(Vec::<String>::new());

view()
    .min_h(px(160.0))
    .p_6()
    .on_file_drag_enter(move || hovering.set(true))
    .on_file_drag_leave(move || hovering.set(false))
    .on_file_drop(move |event: &FileDropEvent| {
        files.set(event.uris.clone());
        hovering.set(false);
    })
    .child(text(move || if hovering.get() { "Release files here" } else { "Drop files here" }))
    .child(text(move || files.get().join("\n")))

Der nächstgelegene aktivierte Vorfahre, der auf onFileDrop lauscht, empfängt den Drop. Ein modales Root begrenzt diese Suche. disabled entfernt die Listener für Datei-Drag-and-Drop; Verlassen, Abbruch, Abschluss und Entfernen des Ziels setzen den aktiven Zustand zurück. Die Annahme verwendet die Copy-Operation und den gemounteten nativen Zustand. Die App entscheidet, ob sie die empfangenen Dateien liest, öffnet oder überträgt.

#Ref-Methoden

Die Ref einer einfachen View ist ein ViewRef: focus(), blur() und measure(). ref (.element_ref(&handle) in Rust) liefert ihren Node, z. B. um ihn an setAccessibilityFocus zu übergeben (Accessibility-API von nsh) oder um sie zu messen (handle.measure() in Rust).

#Views innerhalb von Text

Eine View, die innerhalb eines Textes platziert wird, ist ein Inline-Anhang: Sie sitzt auf der Grundlinie, wird mit den Wörtern umbrochen und nimmt eigene Presses entgegen. Siehe Text.

#Plattformhinweise

  • boxShadow zeichnet auf jeder Plattform; android={{ elevation }} liefert stattdessen Androids Material-Schatten. Auf Android benötigt ein Schatten eine Hintergrundfarbe (eine View mit transparentem Hintergrund wirft keinen).
  • filter auf Views: iOS unterstützt opacity(), brightness() bis 1 und drop-shadow() (Bilder akzeptieren jeden Farbfilter und blur()); Android unterstützt alle ab API 31, darunter die Farbfilter.
  • backdropFilter: "blur(…)" ist auf iOS ein System-Material (eine Annäherung) und erfordert auf Android API 31.
  • Android zeichnet translate-, scale- und rotate-Transformationen; Skews (skew(), skewX(), skewY()) werden auf den anderen Plattformen angewendet.
  • borderCurve: "continuous" (.border_curve(BorderCurve::Continuous)) zeichnet Ecken, die sanft in die Kanten übergehen (wie bei iOS-App-Icons), auf beiden Plattformen.
  • Der Style cursor wird angewendet, wo es einen Zeiger gibt (Mac Catalyst, iPad mit Zeiger, Desktops).