1 /** 2 * Deft framework demo — a localized widget gallery. 3 * 4 * Exercises every control type the framework provides: menus and accelerators, 5 * a status bar, a tab control whose pages hold labels, buttons, check boxes, a 6 * radio group, single- and multi-line text boxes, a list view, a tree view, a 7 * list box and a combo box, plus a one-second timer, a tray icon, a modal 8 * dialog and a message box. 9 * 10 * It also demonstrates **localization**: every user-facing string is marked with 11 * Deft's `tr()` seam, the `Language` menu switches the UI language at runtime, 12 * and translations are loaded from gettext `.mo` catalogs (compiled from the 13 * `.po` files under `locale/`) via the `mofile` package, installed through 14 * `setTranslator`. The standard dialog buttons localize themselves from the OS. 15 * 16 * Each tab page is a framework `Panel` — a container that lays its children out 17 * with a sizer and forwards their notifications. 18 */ 19 module app; 20 21 import std.conv : to; 22 import std.file : thisExePath, exists; 23 import std.format : format; 24 import std.path : buildPath, dirName; 25 26 import core.sys.windows.windows; 27 28 import mofile; 29 30 import deft; 31 32 /// Command ids for the menu bar. 33 enum : int 34 { 35 idNew = 40_001, 36 idInput = 40_002, 37 idExit = 40_003, 38 idPreview = 40_010, 39 idAbout = 40_020, 40 idTrayShow = 40_030, 41 idTrayExit = 40_031, 42 idLangEn = 40_040, 43 idLangFr = 40_041, 44 idLangDe = 40_042, 45 idLangRu = 40_043, 46 idLangUk = 40_044, 47 } 48 49 int main() 50 { 51 auto app = Application.instance; 52 app.initialize(); 53 54 auto window = new Window("Deft Widget Gallery", 900, 640); 55 window.setIcon(loadIcon(1)); 56 window.setMinimumSize(640, 480); 57 58 auto status = new StatusBar(window); 59 window.setStatusBar(status); 60 61 // --- Tab control with two pages, each a Panel laid out with a VBox. --- 62 auto tabs = new TabControl(window); 63 64 auto root = new VBox(); 65 root.add(tabs).proportion(1).pad(Padding.all(8)); 66 window.setSizer(root); 67 68 // Page 1: basic controls. 69 auto basics = new Panel(window); 70 auto basicsBox = new VBox(); 71 72 auto hello = new Label(basics, tr("Hello from Deft")); 73 basicsBox.add(hello).pad(Padding.all(6)); 74 75 auto clock = new Label(basics, ""); 76 basicsBox.add(clock).pad(Padding.all(6)); 77 78 auto dialogButton = new Button(basics, tr("&Open Dialog...")); 79 basicsBox.add(dialogButton).pad(Padding.all(6)).alignH(HAlign.center); 80 81 auto feature = new CheckBox(basics, tr("Enable &feature")); 82 basicsBox.add(feature).pad(Padding.all(6)); 83 84 auto optionA = new RadioButton(basics, tr("Option &A"), true); 85 auto optionB = new RadioButton(basics, tr("Option &B")); 86 optionA.setChecked(true); 87 basicsBox.add(optionA).pad(Padding.all(6)); 88 basicsBox.add(optionB).pad(Padding.all(6)); 89 90 auto search = new TextBox(basics, "", TextBoxStyle.singleLine); 91 basicsBox.add(search).pad(Padding.all(6)); 92 93 auto notes = new TextBox(basics, tr("Multi-line text..."), TextBoxStyle.multiLine); 94 basicsBox.add(notes).proportion(1).pad(Padding.all(6)); 95 96 basics.setSizer(basicsBox); 97 tabs.addPage(tr("Basics"), basics); 98 99 // Page 2: list-style controls. 100 auto lists = new Panel(window); 101 auto listsBox = new VBox(); 102 103 auto listView = new ListView(lists); 104 listView.addColumn(tr("Title"), 260); 105 listView.addColumn(tr("Updated"), 140); 106 listView.addColumn(tr("Created"), 140); 107 listView.addItem(["My first note", "2026-05-01", "2026-04-15"]); 108 listView.addItem(["Shopping list", "2026-04-30", "2026-04-20"]); 109 listView.addItem(["Meeting agenda", "2026-04-28", "2026-04-10"]); 110 listView.autoSizeColumn(0, ColumnAutoSize.content); 111 listView.autoSizeColumn(1, ColumnAutoSize.content); 112 listView.autoSizeColumn(2, ColumnAutoSize.header); 113 setAccessibleName(listView, tr("Notes list")); 114 listsBox.add(listView).proportion(2).pad(Padding.all(6)); 115 116 auto tree = new TreeView(lists); 117 auto work = tree.addRoot("Work"); 118 tree.addChild(work, "Project Alpha"); 119 tree.addChild(work, "Project Beta"); 120 auto personal = tree.addRoot("Personal"); 121 tree.addChild(personal, "Shopping"); 122 tree.addChild(personal, "Travel"); 123 tree.expandItem(work); 124 setAccessibleName(tree, tr("Categories")); 125 listsBox.add(tree).proportion(2).pad(Padding.all(6)); 126 127 auto listBox = new ListBox(lists); 128 listBox.addItem("Apples"); 129 listBox.addItem("Oranges"); 130 listBox.addItem("Pears"); 131 setAccessibleName(listBox, tr("Fruit list")); 132 listsBox.add(listBox).proportion(1).pad(Padding.all(6)); 133 134 auto checks = new CheckListBox(lists); 135 checks.addItem("Bold"); 136 checks.addItem("Italic"); 137 checks.addItem("Underline"); 138 checks.setChecked(0, true); 139 setAccessibleName(checks, tr("Text style")); 140 checks.onItemChecked ~= (int index) { 141 status.setText(format(checks.isChecked(index) ? tr("Checked: %s") : tr("Unchecked: %s"), 142 checks.getItemText(index))); 143 }; 144 listsBox.add(checks).proportion(1).pad(Padding.all(6)); 145 146 auto combo = new ComboBox(lists); 147 combo.addItem("Small"); 148 combo.addItem("Medium"); 149 combo.addItem("Large"); 150 combo.setSelectedIndex(1); 151 setAccessibleName(combo, tr("Size")); 152 listsBox.add(combo).pad(Padding.all(6)); 153 154 lists.setSizer(listsBox); 155 tabs.addPage(tr("Lists"), lists); 156 157 // --- Localization state and machinery. --- 158 int elapsed = 0; 159 MoFile catalog; 160 bool haveCatalog = false; 161 MenuBar menuBar; // assigned once the menu is built; used by retranslate() 162 163 /// Refresh the clock label in the current language. 164 void updateClock() 165 { 166 clock.setText(format(tr("Elapsed: %d s"), elapsed)); 167 } 168 169 /// Re-apply every translatable caption in the active language. Controls 170 /// created on demand (dialogs, message boxes) read `tr()` when shown, so they 171 /// need no retranslation here. 172 void retranslate() 173 { 174 window.setTitle(tr("Deft Widget Gallery")); 175 status.setText(tr("Ready")); 176 177 tabs.setTabTitle(0, tr("Basics")); 178 tabs.setTabTitle(1, tr("Lists")); 179 180 hello.setText(tr("Hello from Deft")); 181 updateClock(); 182 dialogButton.setText(tr("&Open Dialog...")); 183 feature.setText(tr("Enable &feature")); 184 optionA.setText(tr("Option &A")); 185 optionB.setText(tr("Option &B")); 186 187 listView.setColumnTitle(0, tr("Title")); 188 listView.setColumnTitle(1, tr("Updated")); 189 listView.setColumnTitle(2, tr("Created")); 190 191 if (menuBar !is null) 192 { 193 menuBar.setMenuTitle(0, tr("&File")); 194 menuBar.setMenuTitle(1, tr("&Edit")); 195 menuBar.setMenuTitle(2, tr("&Language")); 196 menuBar.setMenuTitle(3, tr("&Help")); 197 menuBar.setItemText(idNew, tr("&New Note...")); 198 menuBar.setItemText(idInput, tr("&Input...")); 199 menuBar.setItemText(idExit, tr("E&xit")); 200 menuBar.setItemText(idPreview, tr("Show &Preview")); 201 menuBar.setItemText(idAbout, tr("&About")); 202 if (window.handle) 203 DrawMenuBar(window.handle); 204 } 205 } 206 207 /// Load `code` ("en" for the untranslated source language, otherwise a locale 208 /// directory under `locale/`) and re-translate the whole UI. 209 void loadLanguage(string code) 210 { 211 if (code == "en") 212 { 213 haveCatalog = false; 214 setTranslator(null); 215 } 216 else 217 { 218 auto path = buildPath(dirName(thisExePath()), "locale", code, "deft-demo.mo"); 219 if (exists(path)) 220 { 221 try 222 { 223 catalog = MoFile(path); 224 haveCatalog = true; 225 setTranslator((string key) => catalog.gettext(key)); 226 } 227 catch (Exception) 228 { 229 haveCatalog = false; 230 setTranslator(null); 231 } 232 } 233 else 234 { 235 haveCatalog = false; 236 setTranslator(null); 237 } 238 } 239 240 if (menuBar !is null) 241 { 242 menuBar.setChecked(idLangEn, code == "en"); 243 menuBar.setChecked(idLangFr, code == "fr"); 244 menuBar.setChecked(idLangDe, code == "de"); 245 menuBar.setChecked(idLangRu, code == "ru"); 246 menuBar.setChecked(idLangUk, code == "uk"); 247 } 248 249 retranslate(); 250 } 251 252 // --- Cross-control wiring. --- 253 search.onTextChanged ~= (string text) { 254 status.setText(format(tr("Search: %s"), text)); 255 }; 256 257 listView.onSelectionChanged ~= (int index) { 258 status.setText(format(tr("Selected: %s"), listView.getItemText(index, 0))); 259 }; 260 listView.onItemActivated ~= (int index) { 261 showMessageBox(window, 262 format(tr("You activated: %s"), listView.getItemText(index, 0)), 263 tr("Note"), MessageBoxStyle.info); 264 }; 265 266 // Right-click context menu on the list (coordinates are screen-relative). 267 auto listMenu = new Menu(); 268 auto ctxView = MenuItem(0, tr("&View")); 269 ctxView.onClicked ~= { 270 int sel = listView.getSelectedIndex(); 271 if (sel >= 0) 272 status.setText(format(tr("View: %s"), listView.getItemText(sel, 0))); 273 }; 274 listMenu.append(ctxView); 275 listView.onContextMenu ~= (int index, MouseEventArgs m) { 276 if (index >= 0) 277 listView.setSelectedIndex(index); 278 showPopupMenu(listMenu, window, m.x, m.y); 279 }; 280 281 tree.onSelectionChanged ~= (TreeItem item) { 282 status.setText(format(tr("Category: %s"), tree.getItemText(item))); 283 }; 284 285 // Context menu on the tree — works from right-click and the Apps key / Shift+F10. 286 auto treeMenu = new Menu(); 287 auto ctxExpand = MenuItem(0, tr("&Expand")); 288 ctxExpand.onClicked ~= { 289 auto sel = tree.getSelectedItem(); 290 if (!sel.isNull) 291 tree.expandItem(sel); 292 }; 293 treeMenu.append(ctxExpand); 294 tree.onContextMenu ~= (TreeItem item, MouseEventArgs m) { 295 if (!item.isNull) 296 tree.setSelectedItem(item); 297 showPopupMenu(treeMenu, window, m.x, m.y); 298 }; 299 300 listBox.onSelectionChanged ~= (int index) { 301 status.setText(format(tr("Fruit: %s"), listBox.getItemText(index))); 302 }; 303 304 feature.onToggled ~= { 305 status.setText(feature.isChecked() ? tr("Feature on") : tr("Feature off")); 306 }; 307 308 dialogButton.onClicked ~= { openEditDialog(window, status); }; 309 310 // --- Menu bar with accelerators. --- 311 auto fileMenu = new Menu(); 312 auto newItem = MenuItem(idNew, tr("&New Note..."), "Ctrl+N"); 313 newItem.onClicked ~= { status.setText(tr("New note")); }; 314 fileMenu.append(newItem); 315 316 auto inputItem = MenuItem(idInput, tr("&Input..."), "Ctrl+I"); 317 inputItem.onClicked ~= { 318 auto answer = showInputDialog(window, tr("Your name"), tr("Enter your &name:")); 319 if (answer !is null) 320 status.setText(format(tr("Hello, %s"), answer)); 321 }; 322 fileMenu.append(inputItem); 323 324 fileMenu.appendSeparator(); 325 326 auto exitItem = MenuItem(idExit, tr("E&xit"), "Ctrl+Q"); 327 exitItem.onClicked ~= { window.close(); }; 328 fileMenu.append(exitItem); 329 330 auto editMenu = new Menu(); 331 auto previewItem = MenuItem(idPreview, tr("Show &Preview"), "", MenuItemKind.checkable); 332 previewItem.onClicked ~= { 333 bool now = !editMenu.findItem(idPreview).checked; 334 editMenu.setChecked(idPreview, now); 335 status.setText(now ? tr("Preview on") : tr("Preview off")); 336 }; 337 editMenu.append(previewItem); 338 339 // Language menu: item labels are autonyms, shown in their own language, so 340 // they are intentionally NOT passed through tr(). 341 auto langMenu = new Menu(); 342 auto langEn = MenuItem(idLangEn, "English", "", MenuItemKind.checkable); 343 langEn.onClicked ~= { loadLanguage("en"); }; 344 langMenu.append(langEn); 345 auto langFr = MenuItem(idLangFr, "Français", "", MenuItemKind.checkable); 346 langFr.onClicked ~= { loadLanguage("fr"); }; 347 langMenu.append(langFr); 348 auto langDe = MenuItem(idLangDe, "Deutsch", "", MenuItemKind.checkable); 349 langDe.onClicked ~= { loadLanguage("de"); }; 350 langMenu.append(langDe); 351 auto langRu = MenuItem(idLangRu, "Русский", "", MenuItemKind.checkable); 352 langRu.onClicked ~= { loadLanguage("ru"); }; 353 langMenu.append(langRu); 354 auto langUk = MenuItem(idLangUk, "Українська", "", MenuItemKind.checkable); 355 langUk.onClicked ~= { loadLanguage("uk"); }; 356 langMenu.append(langUk); 357 358 auto helpMenu = new Menu(); 359 auto aboutItem = MenuItem(idAbout, tr("&About"), "F1"); 360 aboutItem.onClicked ~= { 361 showMessageBox(window, 362 tr("Deft Widget Gallery\nA native UI framework for D."), 363 tr("About"), MessageBoxStyle.info); 364 }; 365 helpMenu.append(aboutItem); 366 367 menuBar = new MenuBar(); 368 menuBar.append(fileMenu, tr("&File")); 369 menuBar.append(editMenu, tr("&Edit")); 370 menuBar.append(langMenu, tr("&Language")); 371 menuBar.append(helpMenu, tr("&Help")); 372 window.setMenuBar(menuBar); 373 374 // --- One-second timer updating the clock label. --- 375 auto timer = new Timer(window); 376 timer.onTick ~= { 377 ++elapsed; 378 updateClock(); 379 }; 380 timer.start(1000); 381 382 // --- Tray icon with a context menu. --- 383 auto trayMenu = new Menu(); 384 auto trayShow = MenuItem(idTrayShow, tr("&Show")); 385 trayShow.onClicked ~= { 386 window.show(); 387 SetForegroundWindow(window.handle); 388 }; 389 trayMenu.append(trayShow); 390 trayMenu.appendSeparator(); 391 auto trayExit = MenuItem(idTrayExit, tr("E&xit")); 392 auto tray = new TrayIcon(window, tr("Deft Widget Gallery")); 393 trayExit.onClicked ~= { 394 tray.remove(); 395 app.quit(); 396 }; 397 trayMenu.append(trayExit); 398 399 tray.setIcon(LoadIconW(null, IDI_APPLICATION)); 400 tray.setContextMenu(trayMenu); 401 tray.onDoubleClicked ~= { 402 window.show(); 403 SetForegroundWindow(window.handle); 404 }; 405 406 // Remove the tray icon before the window is destroyed. 407 window.onClose ~= (CloseEventArgs* args) { tray.remove(); }; 408 409 // Start in the source language (English); checks the English menu item and 410 // fills in the dynamic clock/status captions. 411 loadLanguage("en"); 412 413 window.show(); 414 return app.run(); 415 } 416 417 /// Open a modal note editor and report the result to the status bar. 418 void openEditDialog(Window parent, StatusBar status) 419 { 420 auto dialog = new Dialog(parent, tr("Edit Note"), 480, 360); 421 scope (exit) 422 dialog.dispose(); 423 424 // A table layout: a fixed label column with right-aligned labels, and a 425 // stretching field column; the title row sizes to the field, the content row 426 // fills the rest. 427 auto grid = new Grid(2, 2); 428 grid.setColumn(0, GridTrack.pixels(90)); 429 grid.setColumn(1, GridTrack.percent(100)); 430 grid.setRow(0, GridTrack.autoSize); 431 grid.setRow(1, GridTrack.percent(100)); 432 grid.setSpacing(8, 8); 433 434 auto titleLabel = new Label(dialog, tr("&Title:")); 435 auto titleInput = new TextBox(dialog, tr("Untitled")); 436 auto bodyLabel = new Label(dialog, tr("&Content:")); 437 auto bodyInput = new TextBox(dialog, "", TextBoxStyle.multiLine); 438 439 grid.add(titleLabel, 0, 0).aligned(HAlign.right, VAlign.middle).pad(Padding.all(4)); 440 grid.add(titleInput, 1, 0).pad(Padding.all(4)); 441 grid.add(bodyLabel, 0, 1).aligned(HAlign.right, VAlign.top).pad(Padding.all(4)); 442 grid.add(bodyInput, 1, 1).pad(Padding.all(4)); 443 444 dialog.setSizer(grid); 445 dialog.addStandardButtons(ButtonSet.okCancel); 446 447 if (dialog.showModal() == DialogResult.ok) 448 status.setText(format(tr("Saved: %s"), titleInput.getText())); 449 else 450 status.setText(tr("Edit canceled")); 451 }