شاشات كاملة
الشاشات
لا قصاصات ولا نماذج مزيفة. كل واحدة من هذه نافذة رسمها الطقم أثناء بناء هذا الموقع.
التطبيق التوضيحي
شاشة طلبات — جدول يفرز ويصفّي، ويُفتح كل طلب في لوحة تنزلق بجانبه — وعلى الجانب كل المكوّنات مصنّفة حسب الفئة. المنتقيات الأربعة في الأعلى تبدّل لغة النافذة كلها واتجاهها وحجمها وسمتها.
jbang demo-toolkit/limn-toolkitjbang --java-options=-XstartOnFirstThread demo-toolkit/limn-toolkit

النافذة نفسها، من اليمين إلى اليسار
التطبيق التوضيحي تحت حزمة ar وقد قُلب محور الاتجاه: شريط القوائم والتنقل والحقول وأشرطة التمرير والجدول كلها تُقرأ من اليمين، والنص داخلها يتصل ويُعاد ترتيبه كما ينبغي للعربية. لم تُعد كتابة أي شاشة — الاتجاه محور واحد موروث، يُضبط كما تُضبط درجة حجم عنصر التحكم.


نموذج
تسميات، وحقل مُتحقَّق منه، واختيار، ومفتاح تبديل، وصف الأزرار: المثال المشروح من دليل النماذج.


الشيفرة
public static Scene scene() {
Row centred = new Row();
centred.mainAlignment(Flex.MainAlignment.CENTER)
.crossAlignment(Flex.CrossAlignment.CENTER);
centred.add(new SizedBox(420, SizedBox.UNSET, form()));
Scene scene = new Scene(new Padding(Insets.all(32), centred));
return scene;
}
public static Widget<?> form() {
TextField name = new TextField();
name.setText("Ada Lovelace");
TextField email = new TextField();
email.setText("ada@example");
Field emailField = emailField(email);
validate(email, emailField);
// setText above was the application's write, and a handler runs for the user alone, so
// the rule in validate did not run: the starting state is set here, explicitly.
emailField.setValidation(Validation.ERROR, "Enter an address like ada@example.com");
PasswordField password = new PasswordField();
password.setText("correct horse battery");
ComboBox<String> plan = new ComboBox<>(List.of("Personal", "Team", "Enterprise"));
plan.setSelectedIndex(1);
Checkbox updates = new Checkbox(Checkbox.Variant.SWITCH, "Email me release notes");
updates.setChecked(true);
Column form = new Column();
form.gap(18).crossAlignment(Flex.CrossAlignment.STRETCH);
form.add(new Label("Create your account").setRole(Label.Role.TITLE));
form.add(counted(new Field("Full name", name).setRequired(true), name, 40));
form.add(emailField);
form.add(new Field("Password", password).setHelperText("At least 12 characters"));
form.add(new Field("Plan", plan));
form.add(updates);
form.add(Separator.horizontal());
form.add(actions());
return form;
}
static Field emailField(TextField email) {
return new Field("Email", email)
.setHelperText("The receipt goes here")
.setRequired(true);
}
static Field counted(Field field, TextField text, int limit) {
field.setHelperText(text.text().length() + " / " + limit);
text.observeChanges((widget, change) -> {
if (change.aspect() == Change.Aspect.TEXT) {
field.setHelperText(text.text().length() + " / " + limit);
}
});
return field;
}
static void validate(TextField email, Field field) {
email.onChange(text -> {
if (text.matches("[^@\\s]+@[^@\\s]+\\.[^@\\s]+")) {
field.setValidation(Validation.SUCCESS);
} else {
field.setValidation(Validation.ERROR, "Enter an address like ada@example.com");
}
});
}
static Widget<?> actions() {
Row actions = new Row();
actions.gap(12).crossAlignment(Flex.CrossAlignment.CENTER);
actions.add(Expanded.spacer(1));
actions.add(new Button("Cancel").setStyle(Button.Style.SECONDARY));
actions.add(new Button("Create account"));
return actions;
}نافذة مخططة
شريط أدوات، وشريط جانبي إلى جوار لوح المحتوى، وسطر حالة: المثال المشروح من دليل التخطيط.


الشيفرة
public static Scene scene() {
Scene scene = new Scene(shell());
return scene;
}
public static Widget<?> shell() {
ToolBar bar = new ToolBar();
bar.addItem(new Button("Import"));
bar.addSeparator();
bar.addItem(new Button("New collection").setStyle(Button.Style.SECONDARY));
bar.addItem(new Button("Share").setStyle(Button.Style.SECONDARY));
SplitPane panes = SplitPane.horizontal(sidebar(), library());
panes.setRatio(0.24f).setMinimums(150, 320);
Row status = spread(
new Label("18 assets · 3 selected").setMuted(true),
new Label("Synced 2 minutes ago").setMuted(true));
return column(0,
bar,
Expanded.of(panes, 1),
Separator.horizontal(),
new Padding(Insets.symmetric(8, 14), status));
}
private static Widget<?> sidebar() {
return column(0,
Expanded.of(collections(), 1),
Separator.horizontal(),
new Padding(Insets.all(14), details("Textures", "Studio drive")));
}
private static Widget<?> collections() {
ListView<String> list = new ListView<>(
name -> new Padding(Insets.symmetric(9, 14), new Label(name)));
list.setItems(COLLECTIONS);
list.setSelectedIndex(1);
// A list with no caption takes its name directly; a reader says "Collections, list".
list.setAccessibleName("Collections");
return list;
}
private static Widget<?> library() {
SearchField search = new SearchField();
SegmentedControl view = new SegmentedControl(List.of("Grid", "List"));
view.setSelectedIndex(0);
view.setAccessibleName("View");
Row tools = new Row();
tools.gap(10).crossAlignment(Flex.CrossAlignment.CENTER);
tools.add(new SizedBox(190, SizedBox.UNSET, search));
tools.add(view);
List<Widget<?>> tiles = new ArrayList<>();
for (String[] asset : ASSETS) {
tiles.add(tile(asset));
}
return new Padding(Insets.all(20), column(18,
spread(new Label("Textures").setRole(Label.Role.TITLE), tools),
tags(TAGS),
Expanded.of(new ScrollView(tiles(3, tiles)), 1)));
}
private static Widget<?> tile(String[] asset) {
Label badge = new Label(asset[1]);
badge.setColor(Color.WHITE).setStrong(true);
return column(6,
overlay(swatch(asset[3], asset[4]), badge),
new Label(asset[0]).setStrong(true),
new Label(asset[2]).setMuted(true));
}
private static Widget<?> swatch(String from, String to) {
int width = 96;
int height = 64;
Color a = Color.fromHex(from);
Color b = Color.fromHex(to);
byte[] pixels = new byte[width * height * 4];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
float t = (x / (float) (width - 1) + y / (float) (height - 1)) / 2f;
int i = (y * width + x) * 4;
Color mixed = a.lerp(b, t);
pixels[i] = (byte) Color.toByte(mixed.r());
pixels[i + 1] = (byte) Color.toByte(mixed.g());
pixels[i + 2] = (byte) Color.toByte(mixed.b());
pixels[i + 3] = (byte) 0xFF;
}
}
return new ImageView(new Image(width, height, pixels))
.setFit(ImageView.Fit.COVER)
.setPreferredSize(600, 86);
}
private static final List<String> COLLECTIONS =
List.of("All assets", "Textures", "Models", "HDRIs", "Fonts", "Archive");
/** The library's filters, as many and as long as a real collection's would be. */
private static final List<String> TAGS = List.of("Tileable", "PBR", "4K", "Outdoor",
"Brushed metal", "Wood", "Stone", "Organic", "Seamless", "Hand-painted", "Sci-fi panels");
/** name · overlay badge · size on disk · the two ends of the swatch's ramp. */
private static final String[][] ASSETS = {
{"Concrete 04", "2048 × 2048", "4.2 MB", "1E1230", "6D00E0"},
{"Brushed steel", "1024 × 1024", "1.8 MB", "10131E", "5A6E8C"},
{"Oak planks", "2048 × 2048", "3.6 MB", "241206", "9A5A22"},
{"Sand dunes", "4096 × 4096", "18.4 MB", "2A1A08", "C79A4B"},
{"Rust patina", "2048 × 2048", "5.1 MB", "2B0D08", "A8442A"},
{"Marble tile", "2048 × 2048", "3.9 MB", "141626", "8E93C8"},
{"Moss bank", "2048 × 2048", "4.7 MB", "0C1A0E", "3F8A46"},
// Eight, not nine: the last line of the grid is deliberately short, so the
// picture on the guide page shows what a short line does to the tile width.
{"Sea ice", "4096 × 4096", "21.0 MB", "081A22", "6FBFD6"},
};
static Column column(float gap, Widget<?>... children) {
Column column = new Column();
column.gap(gap).crossAlignment(Flex.CrossAlignment.STRETCH);
for (Widget<?> child : children) {
column.add(child);
}
return column;
}
static Row spread(Widget<?> leading, Widget<?> trailing) {
Row row = new Row();
row.gap(12).crossAlignment(Flex.CrossAlignment.CENTER);
row.add(leading);
row.add(Expanded.spacer(1));
row.add(trailing);
return row;
}
static Widget<?> overlay(Widget<?> content, Widget<?> on) {
Stack stack = new Stack();
stack.alignment(Stack.Alignment.CENTER);
stack.add(content);
stack.add(on);
return stack;
}
static Wrap tags(List<String> names) {
Wrap tags = new Wrap();
tags.gap(8).runGap(8);
tags.setControlSize(ControlSize.SMALL);
for (String name : names) {
tags.add(new Button(name).setStyle(Button.Style.SECONDARY));
}
return tags;
}
static Grid details(String name, String location) {
TextField nameField = new TextField();
nameField.setText(name);
TextField locationField = new TextField();
locationField.setText(location);
Checkbox shared = new Checkbox(Checkbox.Variant.BOX, "Shared");
shared.setChecked(true);
Grid form = new Grid(2);
form.gap(12).rowGap(10).expandedColumn(1)
.columnAlignment(1, Flex.CrossAlignment.STRETCH);
form.add(new Label("Name").setMuted(true).setLabelFor(nameField));
form.add(nameField);
form.add(new Label("Location").setMuted(true).setLabelFor(locationField));
form.add(locationField);
form.add(new SizedBox(0, 0)); // an empty cell: the checkbox carries its own caption
form.add(shared);
return form;
}
static Column tiles(int perRow, List<Widget<?>> tiles) {
Column rows = new Column();
rows.gap(14).crossAlignment(Flex.CrossAlignment.STRETCH);
for (int start = 0; start < tiles.size(); start += perRow) {
Row line = new Row();
line.gap(14).crossAlignment(Flex.CrossAlignment.START);
for (int i = start; i < Math.min(start + perRow, tiles.size()); i++) {
line.add(Expanded.of(tiles.get(i), 1));
}
for (int i = tiles.size(); i < start + perRow; i++) {
line.add(Expanded.spacer(1));
}
rows.add(line);
}
return rows;
}كل درجات الحجم
عناصر التحكم الخمسة نفسها، خمس مرات، من XSMALL في الأعلى إلى XLARGE في الأسفل. لم يُعطَ أي منها عرضًا ولا خطًا ولا حشوة: كل صف يُخبَر بدرجة حجم لا غير، فتتحرك الحشوة والخط وأنصاف أقطار الزوايا وأهداف اللمس معًا.


منفذ العرض ثلاثي الأبعاد
خامات قائمة على الفيزياء تحت ثلاثة أضواء، مرسومة إلى هدف خطي عالي المدى الديناميكي ومركّبة طبقةً ثنائية الأبعاد. عرض التمرير يقصّه كأي عنصر آخر. اسحب لتدور حوله، ومرّر لتقرّب.


محرر السمات، برنامج يكتب ملفًا
اللوحة بيانات، فتحريرها شاشة؛ وهذه برنامج تشغّله، لا اعتمادية يحملها تطبيقك. يحفظ ملف سمة، يقرؤه تطبيقك بواسطة ThemeFormat من limn-toolkit وحده. اسحب منزلق الزوايا فتُعاد كسوة النافذة في الإطار نفسه: كل حقل وزر وحوض في الصورة. والتقرير المجاور يقيس كل حبر على كل سطح يمكن أن يقع عليه، فاللوحة التي تفشل في التباين تفشل على مرأى.
jbang theme-editor-toolkit/limn-toolkitjbang --java-options=-XstartOnFirstThread theme-editor-toolkit/limn-toolkit


