Skip to content

The desktop

Besides its windows, a desktop app can have an icon in the menu bar (macOS) or the taskbar’s notification area (Windows), send notifications, fill the screen, and hand the user files to drag onto the desktop or into other apps. On other systems (and phones), these do nothing.

.trayIcon gives the app an icon with a menu, in the menu bar’s right side on macOS and the taskbar’s tray on Windows:

app Uploader(width: 360, height: 240) {
state uploads = 0
Text("{uploads} files sent")
.trayIcon(.upload, title: "{uploads}", keepRunning: true) {
MenuItem("Send a file…") {
if let path = openFile(title: "Send") {
print("sending {path}")
uploads += 1
}
}
MenuItem("Show window") { showWindow() }
MenuDivider()
MenuItem("Quit") { quit() }
}
}
.trayIcon(_ icon: IconName, title: String = "", keepRunning: Bool = false, items: View)
  • The icon is one of the built-in icons, drawn in the menu bar’s color on macOS and to suit the taskbar on Windows.
  • title: is text beside the icon on macOS (a count, a timer’s minutes), and the icon’s tooltip on Windows. It follows your state like any text.
  • The items are MenuItems, MenuDividers and Menus, as in a context menu. On macOS a click on the icon opens the menu; on Windows a right click does, and a left click shows the window.
  • Put it on a view of the app’s main window (any view: it’s the app’s). An app has one tray icon.

With keepRunning: true, closing the main window doesn’t end the app: the window is hidden, and the app goes on in its icon (a timer goes on counting, a server goes on serving). showWindow() brings the window back, in front. quit() ends the app.

Give such an app a menu item for each, or the user has no way back to the window and no way to end the app.

showNotification(_ title: String, message: String = "")

Shows a notification from the system, which the user sees even when the app isn’t in front:

Button("Back up now") {
backUp()
showNotification("Backup done", message: "128 files were saved.")
}
  • The first time, the system asks the user whether the app may send notifications (on macOS). If they say no, nothing shows.
  • On macOS, an app built as a bundle (tessel build --bundle) sends them as itself; one run without a bundle (tessel run) sends them through the system’s scripting, which shows them as Script Editor’s.
  • On Windows, they come from the app’s icon in the tray (a hidden one when the app has no .trayIcon). Clicking one shows the main window.
setFullScreen(_ on: Bool)
isFullScreen() -> Bool

setFullScreen(true) makes the main window fill the screen, without its title bar; setFullScreen(false) puts it back. isFullScreen() says which it is (also after the user changed it with the window’s own button).

Button(if isFullScreen() { "Leave full screen" } else { "Full screen" }) {
setFullScreen(!isFullScreen())
}
.dragFile(_ path: String)

A view with .dragFile can be dragged out of the app, as the file at path: onto the desktop or into a folder (which copies it), or into another app (an email, a chat, an editor), which gets the file as if it were dragged from Finder or Explorer.

HStack {
Icon(.fileText)
Text("summary.csv")
}
.padding(8)
.background(.lightGray, radius: 6)
.dragFile(joinPath(tempFolder(), "summary.csv"))
  • Write the file before it’s dragged (when the data changes, or when the view appears). A path to no file drags nothing.
  • The drag starts once the mouse has moved a few points with its button down; a click is still a click.
  • Dropped back on the app, it comes to .onDrop like any file.

A headless run does none of these; it says what it would do:

trayPrints the tray icon, its title and its menu.
tray "Show window"Chooses an item of its menu.
A notificationPrints (notification "Title" "message").
A window closed into the traytree shows Root (hidden).
dragto "summary.csv" "…"Prints (drag file "…" out).