मुख्य सामग्री पर जाएँ
CrossFly 100% ओपन सोर्सGitHub पर फ़ॉलो करें
CrossFlyCrossFly
शुरू करें
सभी उदाहरण
ऐपडेटासूचियाँफ़ॉर्म2025 पंक्तियाँ · 11 फ़ाइलें

Depot — offline-first CRUD

One shell, three listings over a local database, a detail and a long form. A thing you navigate away from is a Window; a thing you switch between is not.

यह क्या दिखाता है

Depot — the offline-first CRUD app, and the entry point of this example.

gradlew lwjgl3:run --args="classname dev.cf.app.crud.CrudApp"

The shape

One shell Window hosting four destinations, three of which are listings over a local SQLite database, plus a detail overlay and a long form. It is the shape most business apps end up being, and the four decisions below are the ones that are expensive to change later.

1 · The shell is one Window; the destinations are Components

TabBar hosts them and keeps them alive across switches, so a tab the user comes back to is where they left it. Overlays — PartDetailScreen, PartFormScreen — are separate Windows shown on top (new X().show(), back is hide()). The rule that follows from this is short: a thing you navigate away from is a Window; a thing you switch between is not.

2 · The store is the source of truth, and it is local

Every screen reads Repository and nothing else. There is no in-memory cache next to it, because two caches disagree the moment one screen writes. After a write, screens call refreshAll() and re-read — which is cheap, correct, and does not need a change-event system to be invented for it.

3 · The first run has a screen of its own

FirstLoadScreen holds navigation while the store fills, and it is the only screen that can fail visibly. Every other screen in the app is instant by construction.

4 · Unsent work is visible

A local edit shows a "not sent" badge on the card, on the detail and as a number on OutboxTab. The user never has to guess whether something arrived.

What is deliberately missing

Login, permissions, a real transport, conflict resolution, and paging. Each is a genuine subject and each would double this file's size without teaching anything about the shape. The seam for the transport is flushOutbox(); the seam for paging is Items, which counts and renders rather than taking a list, precisely so a listing can show more than it has loaded.

अपनी मशीन पर चलाएँ

हर उदाहरण एक साधारण Window है, इसलिए लॉन्चर को सिर्फ़ उसका नाम चाहिए।

gradlew lwjgl3:run --args="classname dev.cf.app.crud.CrudApp"
इसे बदलना

Java पहले से संकलित होता है, इसलिए ब्राउज़र आपके बदलाव को खुद दोबारा नहीं बना सकता — यहाँ दिखा कोड प्रकाशित होने से पहले TeaVM द्वारा JavaScript में बदला गया था। फ़ाइल को किसी प्रोजेक्ट में कॉपी कीजिए और वह चारों प्लेटफ़ॉर्म पर बिना बदलाव चलती है; ऊपर दी गई कमांड ही पूरी तैयारी है।

यहीं चल रहा हैCrossFly 0.1.32
ऐप शुरू हो रहा है…

TeaVM ने इसे JavaScript में संकलित किया — यह वही कोड है, उसका वीडियो नहीं। · सभी उदाहरण

यह क्या इस्तेमाल करता है

इस उदाहरण में छुए गए फ़्रेमवर्क टाइप। जिनमें लिंक है उनका संदर्भ पृष्ठ मौजूद है।

AlertBadgeButtonCardComponentDataGridDateTimeUtilsDebugDeviceDropdownGapGridLayoutHintLabelListingMainMaterialIconPalColorProgressRelativeScreenSkinSqliteCursorSqliteDbStylesTabBarTextAreaTextFieldVerticalLayoutWidgetsWindow

सोर्स कोड

पूरा, बिना कटौती के। यह वही फ़ाइल है, उसका अंश नहीं।

कच्चा
dev/cf/app/crud/CrudApp.java
package dev.cf.app.crud;

import dev.cf.Described;
import dev.cf.app.AppBar;
import dev.cf.app.AppTheme;
import dev.crossfly.ui.*;
import dev.crossfly.ui.graphics.*;
import dev.crossfly.ui.layout.*;
import dev.crossfly.ui.skin.Skin;
import dev.crossfly.ui.style.*;

/**
 * <b>Depot</b> — the offline-first CRUD app, and the entry point of this example.
 *
 * <pre>
 * gradlew lwjgl3:run --args="classname dev.cf.app.crud.CrudApp"
 * </pre>
 *
 * <h2>The shape</h2>
 *
 * <p>One shell Window hosting four destinations, three of which are listings over a local SQLite
 * database, plus a detail overlay and a long form. It is the shape most business apps end up
 * being, and the four decisions below are the ones that are expensive to change later.</p>
 *
 * <h3>1 · The shell is one Window; the destinations are Components</h3>
 *
 * <p>{@link TabBar} hosts them and keeps them alive across switches, so a tab the user comes back to
 * is where they left it. Overlays — {@link PartDetailScreen}, {@link PartFormScreen} — are separate
 * Windows shown on top ({@code new X().show()}, back is {@code hide()}). The rule that follows from
 * this is short: <b>a thing you navigate away from is a Window; a thing you switch between is
 * not.</b></p>
 *
 * <h3>2 · The store is the source of truth, and it is local</h3>
 *
 * <p>Every screen reads {@link Repository} and nothing else. There is no in-memory cache next to it,
 * because two caches disagree the moment one screen writes. After a write, screens call
 * {@link #refreshAll()} and re-read — which is cheap, correct, and does not need a change-event
 * system to be invented for it.</p>
 *
 * <h3>3 · The first run has a screen of its own</h3>
 *
 * <p>{@link FirstLoadScreen} holds navigation while the store fills, and it is the only screen that
 * can fail visibly. Every other screen in the app is instant by construction.</p>
 *
 * <h3>4 · Unsent work is visible</h3>
 *
 * <p>A local edit shows a "not sent" badge on the card, on the detail and as a number on
 * {@link OutboxTab}. The user never has to guess whether something arrived.</p>
 *
 * <h2>What is deliberately missing</h2>
 *
 * <p>Login, permissions, a real transport, conflict resolution, and paging. Each is a genuine
 * subject and each would double this file's size without teaching anything about the shape. The
 * seam for the transport is {@link Repository#flushOutbox()}; the seam for paging is
 * {@link Listing.Items}, which counts and renders rather than taking a list, precisely so a listing
 * can show more than it has loaded.</p>
 */
public class CrudApp extends Window implements Described
{
   /** Destination indices, in bottom-nav order. */
   public static final int TAB_PARTS = 0, TAB_SUPPLIERS = 1, TAB_ORDERS = 2, TAB_OUTBOX = 3;

   private static final int IC_PARTS = 0xE1DB, IC_SUPPLIERS = 0xE8D1, IC_ORDERS = 0xE8CC,
      IC_OUTBOX = 0xE2C3;

   /** Bottom-nav glyphs read better a little larger than body text. */
   private static final float NAV_ICON = 1.5f;

   private TabBar tabs;
   private PartsTab parts;
   private SuppliersTab suppliers;
   private OrdersTab orders;
   private OutboxTab outbox;

   @Override
   public String title()
   {
      return "Depot — offline-first CRUD";
   }

   @Override
   public String note()
   {
      return "One shell, three listings over a local database, a detail and a long form. A"
         + " thing you navigate away from is a Window; a thing you switch between is not.";
   }

   /** Hands the palette back to whatever was in force before this app opened. */
   @Override
   public void onRemove()
   {
      super.onRemove();
      AppTheme.leave();
   }

   @Override
   public void onAdd(boolean firstTime) throws Exception
   {
      if (!firstTime)
         return;
      // The brand FIRST: fullscreenColor below reads a token, and reading it before the palette
      // is declared bakes in the skin's neutral grey for the life of the window.
      AppTheme.enter();
      // The PAGE, not the surface. Cards, buttons and fields are all drawn on the surface, so a
      // page painted the same colour makes every one of their edges disappear. Read through
      // onRestyle so a light/dark flip re-derives it instead of keeping the old mode's value.
      fullscreenColor = AppTheme.pageColor();
      onRestyle(() -> fullscreenColor = AppTheme.pageColor());
      setLayout(new VerticalLayout(Position.START, Position.START));
      // Nothing is built until the store answers. The gate runs the check off the paint thread and
      // shows itself only when there is work; on a warm store this callback fires within a frame or
      // two and the user never sees a loading screen.
      FirstLoadScreen.gate(this::buildShell);
   }

   private void buildShell()
   {
      try
      {
         parts = new PartsTab(this);
         suppliers = new SuppliersTab(this);
         orders = new OrdersTab(this);
         outbox = new OutboxTab(this);

         tabs = new TabBar(new TabBar.Tab("Parts", MaterialIcon.of(IC_PARTS, NAV_ICON)),
            new TabBar.Tab("Suppliers", MaterialIcon.of(IC_SUPPLIERS, NAV_ICON)),
            new TabBar.Tab("Orders", MaterialIcon.of(IC_ORDERS, NAV_ICON)),
            new TabBar.Tab("Outbox", MaterialIcon.of(IC_OUTBOX, NAV_ICON)));
         tabs.setContent(parts, suppliers, orders, outbox);
         // Entering a destination re-reads the store: the user may have edited a part two tabs ago,
         // and a listing that shows what was true when it was last built is a listing that lies.
         tabs.onTabChanged(i -> refreshAll());
         // A TabBar is a tab strip by default — tabs on TOP, the desktop idiom. A phone app wants
         // them at the bottom, within reach of a thumb, and that is one flag on the skin's own
         // style rather than a style of our own: everything else (shape, typography, the tonal pill
         // behind the active icon) keeps coming from the skin.
         tabs.style(Skin.get()
            .tabBar()
            .copy()
            .tabsAtBottom(true));
         tabs.testTag("crud.tabs");
         // Opened from a menu, the app needs a way back to it; as the whole app (the root
         // window) it has none to go to, and each tab already titles itself.
         if (!isRoot())
            add(0f, new AppBar("Depot", this::hide));
         add(LAY_FILL, tabs);

         // The shell was built after onAdd returned, so the layout it already computed is stale.
         setValidLayout(false);
         Screen.repaint();
         updateBadge();
      }
      catch (Exception e)
      {
         dev.crossfly.util.Debug.handle(e);
      }
   }

   /**
    * Re-reads the store into every destination.
    *
    * <p>Called after any write, and when a destination is entered. Blunt on purpose: at this size
    * a full re-read costs less than the machinery needed to work out which screens a given write
    * affected, and it cannot be wrong. Introduce something finer when a measurement, not a
    * suspicion, says it is needed.</p>
    */
   public void refreshAll()
   {
      if (parts == null)
         return;
      parts.reload();
      suppliers.reload();
      orders.reload();
      outbox.reload();
      updateBadge();
      Screen.repaint();
   }

   /** Puts the pending count on the Outbox tab, or clears it when there is nothing waiting. */
   private void updateBadge()
   {
      int n = Repository.get()
         .pendingCount();
      tabs.badge(TAB_OUTBOX, n == 0 ? null : "" + n);
   }

   /**
    * Switches destinations from a child screen.
    *
    * @param index one of the {@code TAB_*} constants
    */
   public void goToTab(int index)
   {
      tabs.setActiveTab(index);
      refreshAll();
   }
}
एआई सहायक के लिए

इस पृष्ठ के मशीन-पठनीय रूप, ताकि मॉडल API का अनुमान लगाने के बजाय उदाहरण पढ़ सके।

यह उदाहरण, JSON मेंयह उदाहरण, Markdown में
पिछलाReading an odometer (OCR)अगलाPulse — a dashboard