Handling of changes
Based on widgets interactions, an application can change the state of the user interface. In the to-do list application, clicking on the button adds a new item to the to-do list. Legacy frameworks usually use an imperative approach where the application mutates the state directly and manually trigger the graphics pipeline. This style often results in the screen being out of sync with the state. This is why modern declarative frameworks use reactive state management. Reactive systems are either fine-grained or coarse. Coarse reactive system defines the interface as a function over the state. Using some diffing algorithm they are able to compute what part of the interface have changed. Fine-grained reactive system registers dependencies between the state and the affected parts of the interface enabling to identify which parts have to be re-layout, re-rendered, or redrawn. Slint uses the fine-grained approach, while frameworks like React Native, Flutter, and SwiftUI use the coarse approach.
The coarse reactive approach implies generating a new widget tree before diffing it with the previous one. While the use of persistent data structures allows reducing the cost of building a new tree, it still represents some cost in terms of memory allocation and seems not suited to the restriction we have. Conversely, the fine-grained reactive approach requires the implementation of reactive data types that register dependencies using callbacks and use an asynchronous executor to propagate the change among the dependent widgets. Changed widgets are then marked as dirty.
For these reasons we decided to not choose a reactive approach and use a modernized imperative approach. The application mutates the widgets via a layer that registers which widget have been potentially updated. Based on this information we are then able to determine which parts of the screen have to be rendered. Our approach is less fine-grained than Slint’s, so we can render and redraw more parts of the screen than Slint could. In contrast to the fine-grained approach, we save memory by avoiding the registration of callbacks.