This is a *huge* update, containing a massive collection of fixes and improvements. Every widget has been audited so that they each have complete API documentation and type annotations; APIs have been updated so that they're internally consistent (e.g., the attribute `max` is now used to describe maximum values everywhere); and every widget is accompanied by a test suite that provides 100% branch coverage.
This release contains a significant number of backwards incompatible changes and updates; details below.
Features
* The Toga API has been fully audited. All APIs now have 100% test coverage, complete API documentation (including type annotations), and are internally consistent. (1903, 1938, 1944, 1946, 1949, 1951, 1955, 1956, 1964, 1969, 1984, 1996, 2011, 2017, 2025, 2029, 2044, 2058, 2075)
* Headings are no longer mandatory for Tree widgets. If headings are not provided, the widget will not display its header bar. (1767)
* Support for custom font loading was added to the GTK, Cocoa and iOS backends. (1837)
* The testbed app has better diagnostic output when running in test mode. (1847)
* A Textual backend was added to support terminal applications. (1867)
* Support for determining the currently active window was added to Winforms. (1872)
* Programmatically scrolling to top and bottom in MultilineTextInput is now possible on iOS. (1876)
* A handler has been added for users confirming the contents of a TextInput by pressing Enter/Return. (1880)
* An API for giving a window focus was added. (1887)
* Widgets now have a `.clear()` method to remove all child widgets. (1893)
* Winforms now supports hiding and re-showing the app cursor. (1894)
* ProgressBar and Switch widgets were added to the Web backend. (1901)
* Missing value handling was added to the Tree widget. (1913)
* App paths now include a `config` path for storing configuration files. (1964)
* A more informative error message is returned when a platform backend doesn't support a widget. (1992)
* The example apps were updated to support being run with `briefcase run` on all platforms. (1995)
* Headings are no longer mandatory Table widgets. (2011)
* Columns can now be added and removed from a Tree. (2017)
* The default system notification sound can be played via `App.beep()`. (2018)
* DetailedList can now respond to "primary" and "secondary" user actions. These may be implemented as left and right swipe respectively, or using any other platform-appropriate mechanism. (2025)
* A DetailedList can now provide a value to use when a row doesn't provide the required data. (2025)
* The accessors used to populate a DetailedList can now be customized. (2025)
* Transformations can now be applied to *any* canvas context, not just the root context. (2029)
* Canvas now provides more `list`-like methods for manipulating drawing objects in a context. (2029)
* On Windows, the default font now follows the system theme. On most devices, this means it has changed from Microsoft Sans Serif 8pt to Segoe UI 9pt. (2029)
* Font sizes are now consistently interpreted as CSS points. On Android, iOS and macOS, this means any numeric font sizes will appear 33% larger than before. The default font size on these platforms is unchanged. (2029)
* MultilineTextInputs no longer show spelling suggestions when in read-only mode. (2136)
* Applications now verify that a main window has been created as part of the `startup()` method. (2047)
* An implementation of ActivityIndicator was added to the Web backend. (2050)
* An implementation of Divider was added to the Web backend. (2051)
* The ability to capture the contents of a window as an image has been added. (2063)
* A PasswordInput widget was added to the Web backend. (2089)
* The WebKit inspector is automatically enabled on all macOS WebViews, provided you're using macOS 13.3 (Ventura) or iOS 16.4, or later. (2109)
* Text input widgets on macOS now support undo and redo. (2151)
* The Divider widget was implemented on Android. (2181)
Bugfixes
* The WinForms event loop was decoupled from the main form, allowing background tasks to run without a main window being present. (750)
* Widgets are now removed from windows when the window is closed, preventing a memory leak on window closure. (1215)
* Android and iOS apps no longer crash if you invoke `App.hide_cursor()` or `App.show_cursor()`. (1235)
* A Selection widget with no items now consistently returns a selected value of `None` on all platforms. (1723)
* macOS widget methods that return strings are now guaranteed to return strings, rather than native Objective C string objects. (1779)
* WebViews on Windows no longer have a black background when they are resized. (1855)
* The interpretation of `MultilineTextInput.readonly` was corrected iOS (1866)
* A window without an `on_close` handler can now be closed using the window frame close button. (1872)
* Android apps running on devices older than API level 29 (Android 10) no longer crash. (1878)
* Missing value handling on Tables was fixed on Android and Linux. (1879)
* The GTK backend is now able to correctly identify the currently active window. (1892)
* Error handling associated with the creation of Intents on Android has been improved. (1909)
* The DetailedList widget on GTK now provides an accurate size hint during layout. (1920)
* Apps on Linux no longer segfault if an X Windows display cannot be identified. (1921)
* The `on_result` handler is now used by Cocoa file dialogs. (1947)
* Pack layout now honors an explicit width/height setting of 0. (1958)
* The minimum window size is now correctly recomputed and enforced if window content changes. (2020)
* The title of windows can now be modified on Winforms. (2094)
* An error on Winforms when a window has no content has been resolved. (2095)
* iOS container views are now set to automatically resize with their parent view (2161)
Backward Incompatible Changes
* The `weight`, `style` and `variant` arguments for `Font` and `Font.register` are now keyword-only. (1903)
* The `clear()` method for resetting the value of a MultilineTextInput, TextInput and PasswordInput has been removed. This method was an ambiguous override of the `clear()` method on Widget that removed all child nodes. To remove all content from a text input widget, use `widget.value = ""`. (1938)
* The ability to perform multiple substring matches in a `Contains` validator has been removed. (1944)
* The `TextInput.validate` method has been removed. Validation now happens automatically whenever the `value` or `validators` properties are changed. (1944)
* The argument names used to construct validators have changed. Error message arguments now all end with `_message`; `compare_count` has been renamed `count`; and `min_value` and `max_value` have been renamed `min_length` and `max_length`, respectively. (1944)
* The `get_dom()` method on WebView has been removed. This method wasn't implemented on most platforms, and wasn't working on any of the platforms where it *was* implemented, as modern web view implementations don't provide a synchronous API for accessing web content in this way. (1949)
* The `evaluate_javascript()` method on WebView has been modified to work in both synchronous and asynchronous contexts. In a synchronous context you can invoke the method and use a functional `on_result` callback to be notified when evaluation is complete. In an asynchronous context, you can await the result. (1949)
* The `on_key_down` handler has been removed from WebView. If you need to catch user input, either use a handler in the embedded JavaScript, or create a `Command` with a key shortcut. (1949)
* The `invoke_javascript()` method has been removed. All usage of `invoke_javascript()` can be replaced with `evaluate_javascript()`. (1949)
* The usage of local `file://` URLs has been explicitly prohibited. `file://` URLs have not been reliable for some time; their usage is now explicitly prohibited. (1949)
* `DatePicker` has been renamed `DateInput`. (1951)
* `TimePicker` has been renamed `TimeInput`. (1951)
* The `on_select` handler on the Selection widget has been renamed `on_change` for consistency with other widgets. (1955)
* The `_notify()` method on data sources has been renamed `notify()`, reflecting its status as a public API. (1955)
* The `prepend()` method was removed from the `ListSource` and `TreeSource` APIs. Calls to `prepend(...)` can be replaced with `insert(0, ...)`. (1955)
* The `insert()` and `append()` APIs on `ListSource` and `TreeSource` have been modified to provide an interface that is closer to that `list` API. These methods previously accepted a variable list of positional and keyword arguments; these arguments should be combined into a single tuple or dictionary. This matches the API provided by `__setitem__()`. (1955)
* Images and ImageViews no longer support loading images from URLs. If you need to display an image from a URL, use a background task to obtain the image data asynchronously, then create the Image and/or set the ImageView `image` property on the completion of the asynchronous load. (1956)
* A row box contained inside a row box will now expand to the full height of its parent, rather than collapsing to the maximum height of the inner box's child content. (1958)
* A column box contained inside a column box will now expand to the full width of its parent, rather than collapsing to the maximum width of the inner box's child content. (1958)
* On Android, the user data folder is now a `data` sub-directory of the location returned by `context.getFilesDir()`, rather than the bare `context.getFilesDir()` location. (1964)
* GTK now returns `~/.local/state/appname/log` as the log file location, rather than `~/.cache/appname/log`. (1964)
* The location returned by `toga.App.paths.app` is now the folder that contains the Python source file that defines the app class used by the app. If you are using a `toga.App` instance directly, this may alter the path that is returned. (1964)
* On Winforms, if an application doesn't define an author, an author of `Unknown` is now used in application data paths, rather than `Toga`. (1964)
* Winforms now returns `%USERPROFILE%/AppData/Local/<Author Name>/<App Name>/Data` as the user data file location, rather than `%USERPROFILE%/AppData/Local/<Author Name>/<App Name>`. (1964)
* Support for SplitContainers with more than 2 panels of content has been removed. (1984)
* Support for 3-tuple form of specifying SplitContainer items, used to prevent panels from resizing, has been removed. (1984)
* The ability to increment and decrement the current OptionContainer tab was removed. Instead of `container.current_tab += 1`, use `container.current_tab = container.current_tab.index + 1` (1996)
* `OptionContainer.add()`, `OptionContainer.remove()` and `OptionContainer.insert()` have been removed, due to being ambiguous with base widget methods of the same name. Use the `OptionContainer.content.append()`, `OptionContainer.content.remove()` and `OptionContainer.content.insert()` APIs instead. (1996)
* The `on_select` handler for OptionContainer no longer receives the `option` argument providing the selected tab. Use `current_tab` to obtain the currently selected tab. (1996)
* `TimePicker.min_time` and `TimePicker.max_time` has been renamed `TimeInput.min` and `TimeInput.max`, respectively. (1999)
* `DatePicker.min_date` and `DatePicker.max_date` has been renamed `DateInput.min` and `DateInput.max`, respectively. (1999)
* `NumberInput.min_value` and `NumberInput.max_value` have been renamed `NumberInput.min` and `NumberInput.max`, respectively. (1999)
* `Slider.range` has been replaced by `Slider.min` and `Slider.max`. (1999)
* Tables now use an empty string for the default missing value, rather than warning about missing values. (2011)
* `Table.add_column()` has been deprecated in favor of `Table.append_column()` and `Table.insert_column()` (2011)
* `Table.on_double_click` has been renamed `Table.on_activate`. (2011, 2017)
* Trees now use an empty string for the default missing value, rather than warning about missing values. (2017)
* The `parent` argument has been removed from the `insert` and `append` calls on `TreeSource`. This improves consistency between the API for `TreeSource` and the API for `list`. To insert or append a row in to a descendant of a TreeSource root, use `insert` and `append` on the parent node itself - i.e., `source.insert(parent, index, ...)` becomes `parent.insert(index, ...)`, and `source.insert(None, index, ...)` becomes `source.insert(index, ...)`. (2017)
* When constructing a DetailedList from a list of tuples, or a list of lists, the required order of values has changed from (icon, title, subtitle) to (title, subtitle, icon). (2025)
* The `on_select` handler for DetailedList no longer receives the selected row as an argument. (2025)
* The handling of row deletion in DetailedList widgets has been significantly altered. The `on_delete` event handler has been renamed `on_primary_action`, and is now *only* a notification that a "swipe left" event (or platform equivalent) has been confirmed. This was previously inconsistent across platforms. Some platforms would update the data source to remove the row; some treated `on_delete` as a notification event and expected the application to handle the deletion. It is now the application's responsibility to perform the data deletion. (2025)
* Support for Python 3.7 was removed. (2027)
* `fill()` and `stroke()` now return simple drawing operations, rather than context managers. If you attempt to use `fill()` or `stroke()` on a context as a context manager, an exception will be raised; using these methods on Canvas will raise a warning, but return the appropriate context manager. (2029)
* The `clicks` argument to `Canvas.on_press` has been removed. Instead, to detect "double clicks", you should use `Canvas.on_activate`. The `clicks` argument has also been removed from `Canvas.on_release`, `Canvas.on_drag`, `Canvas.on_alt_press`, `Canvas.on_alt_release`, and `Canvas.on_alt_drag`. (2029)
* The `new_path` operation has been renamed `begin_path` for consistency with the HTML5 Canvas API. (2029)
* Methods that generate new contexts have been renamed: `context()`, `closed_path()`, `fill()` and `stroke()` have become `Context()`, `ClosedPath()`, `Fill()` and `Stroke()` respectively. This has been done to make it easier to differentiate between primitive drawing operations and context-generating operations. (2029)
* A Canvas is no longer implicitly a context object. The `Canvas.context` property now returns the root context of the canvas. If you were previously using `Canvas.context()` to generate an empty context, it should be replaced with `Canvas.Context()`. Any operations to `remove()` drawing objects from the canvas or `clear()` the canvas of drawing objects should be made on `Canvas.context`. Invoking these methods on `Canvas` will now call the base `Widget` implementations, which will throw an exception because `Canvas` widgets cannot have children. (2029)
* The `preserve` option on `Fill()` operations has been deprecated. It was required for an internal optimization and can be safely removed without impact. (2029)
* Drawing operations (e.g., `arc`, `line_to`, etc) can no longer be invoked directly on a Canvas. Instead, they should be invoked on the root context of the canvas, retrieved with via the `canvas` property. Context creating operations (`Fill`, `Stroke` and `ClosedPath`) are not affected. (2029)
* The `tight` argument to `Canvas.measure_text()` has been deprecated. It was a GTK implementation detail, and can be safely removed without impact. (2029)
* The `multiselect` argument to Open File and Select Folder dialogs has been renamed `multiple_select`, for consistency with other widgets that have multiple selection capability. (2058)
* `Window.resizeable` and `Window.closeable` have been renamed `Window.resizable` and `Window.closable`, to adhere to US spelling conventions. (2058)
* Windows no longer need to be explicitly added to the app's window list. When a window is created, it will be automatically added to the windows for the currently running app. (2058)
* The optional arguments of `Command` and `Group` are now keyword-only. (2075)
* In `App`, the properties `id` and `name` have been deprecated in favor of `app_id` and `formal_name` respectively, and the property `module_name` has been removed. (2075)
* `GROUP_BREAK`, `SECTION_BREAK` and `CommandSet` were removed from the `toga` namespace. End users generally shouldn't need to use these classes. If your code *does* need them for some reason, you can access them from the `toga.command` namespace. (2075)
* The `windows` constructor argument of `toga.App` has been removed. Windows are now automatically added to the current app. (2075)
* The `filename` argument and property of `toga.Document` has been renamed `path`, and is now guaranteed to be a `pathlib.Path` object. (2075)
* Documents must now provide a `create()` method to instantiate a `main_window` instance. (2075)
* `App.exit()` now unconditionally exits the app, rather than confirming that the `on_exit` handler will permit the exit. (2075)
Documentation
* Documentation for application paths was added. (1849)
* The contribution guide was expanded to include more suggestions for potential projects, and to explain how the backend tests work. (1868)
* All code blocks were updated to add a button to copy the relevant contents on to the user's clipboard. (1897)
* Class references were updated to reflect their preferred import location, rather than location where they are defined in code. (2001)
* The Linux system dependencies were updated to reflect current requirements for developing and using Toga. (2021)
Misc
* 1865, 1875, 1881, 1882, 1886, 1889, 1895, 1900, 1902, 1906, 1916, 1917, 1918, 1926, 1933, 1948, 1950, 1952, 1954, 1963, 1972, 1977, 1980, 1988, 1989, 1998, 2008, 2014, 2019, 2022, 2028, 2034, 2035, 2039, 2052, 2053, 2055, 2056, 2057, 2059, 2067, 2068, 2069, 2085, 2090, 2092, 2093, 2101, 2102, 2113, 2114, 2115, 2116, 2118, 2119, 2123, 2124, 2127, 2128, 2131, 2132, 2146, 2147, 2148, 2149, 2150, 2163, 2165, 2166, 2171, 2177, 2180, 2184, 2186