Skip to content
Snippets Groups Projects
Commit 22e133a5 authored by Walter Lozano's avatar Walter Lozano Committed by Detlev Casanova
Browse files

Update UI customization


After dropping mildenhall application framework and switching to
agl-compositor and Flatpak much of the UI customization document has
little meaning. Since this document still covers several concepts keep
the conceptual part as a trampoline for further documentation.

Signed-off-by: default avatarWalter Lozano <walter.lozano@collabora.com>
parent 4062bd65
No related branches found
No related tags found
1 merge request!361Improve UI customization doc
Pipeline #321218 passed with warnings
......@@ -13,8 +13,8 @@ outputs = [ "html", "pdf-in",]
date = "2016-05-28"
lastmod = "2021-12-06"
status = "Deprecated"
statusDescription = "Document describes deprecated custom widget library, removed in v2022dev2"
status = ""
statusDescription = ""
+++
# Introduction
......@@ -117,10 +117,6 @@ UI, for example by dragging and dropping them together. The appearance of the
UI in the designer is almost identical to its appearance when it is run in
production.
This could be contrasted with designing a UI by writing a [ClutterScript] file,
for example, where the UI has to be run as part of a program in order to
visualise it.
# Use Cases
A variety of use cases for UI customisation are given below.
......@@ -163,14 +159,14 @@ and previous photos slightly visible at the sides.
### Template Extension
A system integrator wants to use the majority of an Apertis-provided
A system integrator wants to use the majority of a provided
template, but also wants to add their own variant-specific extensions.
The system integrator wants to achieve this without copy and pasting
Apertis-provided templates to retain maintainability, and wants to add
provided templates to retain maintainability, and wants to add
their own extension template which merely references the
Apertis-provided one.
provided one.
For example, said system integrator wants to use an Apertis-provided
For example, said system integrator wants to use an provided
button widget, but wants to make it spin 360° when clicked. They want to
just override the library widget, adding the spin code, and not have to
touch any other code relating to the internal working of the widget
......@@ -301,14 +297,14 @@ want the system to enforce them automatically.
A variety of non-use cases for UI customisation are given below.
## Theming Custom Clutter Widgets
## Theming Custom Widgets
An application developer wants to write their own widget using the
Clutter library directly. They understand that standard variant theming
An application developer wants to write their own widget using a
library directly. They understand that standard variant theming
will not apply to any custom widget and any integration will have to be
achieved manually.
Note that although unsupported directly by the Apertis user interface
Note that although unsupported directly by the user interface
library, it is possible for application authors to implement this higher
up in the application itself.
......@@ -519,7 +515,7 @@ animated so system integrators can tell what can be customised.
## Scripting Support
The widgets and templates should be usable from a UI design format, such
as [GtkBuilder] or [ClutterScript]. This includes custom widgets. This would
as [GtkBuilder]. This includes custom widgets. This would
enable application authors to quickly prototype applications
(see [Prototyping]).
......@@ -626,114 +622,6 @@ source code. These are merely templates and have no way of implementing
logic (if/else statements). If this is required, widget code
customisation is required (see [Custom widgets]).
### Specification in ClutterScript
ClutterScript is a method for creating user interfaces from JSON files.
An example is shown below which describes variant A application chooser
user interface:
```
[{
"id": "model-categories",
"type": "LightwoodAppCategoryModel"
},
{
"id": "model-apps",
"type": "LightwoodAppModel"
},
{
"id": "window",
"type": "LightwoodWindow",
"children": [
{
"id": "roller-categories",
"type": "LightwoodRoller",
"model": "model-categories",
"app-list": "roller-apps",
"signals": [
{ "name": "activated", "handler": "category_activated_cb" }
]
},
{
"id": "roller-apps",
"type": "LightwoodRoller",
"model": "model-apps",
"signals": [
{ "name": "activated", "handler": "app_activated_cb" }
]
}
]
}]
```
The first two objects created (`model-categories` and `model-apps`) are
models for the application categories available on the system, and the
applications available on the system—due to their class names
(`LightwoodAppCategoryModel` and `LightwoodAppModel` respectively). These
models are not widgets visible in the user interface, but proper widgets
will refer to them later in the template.
The next entry describes the main window in the user interface, inside
of which there is a horizontal box (with some style properties set),
with two children that are both of type `LightwoodRoller`. Although these
widgets are of the same type, they are different instances and they have
been given different models. The first roller widget (on the left) has
been given the `model-categories` model and the second roller widget (on
the right) has been given the `model-apps` model, both created at the
beginning of the JSON file.
Additionally the `LightwoodRoller::activated` signal is connected on
both rollers to different callbacks. The signal callback names are
listed in the application documentation. In this case, when the
left-hand roller with categories is changed (activated), the right-hand
roller with applications is updated (set by the `app-list` property).
Another example to compare is given below with the B-variant application
chooser user interface:
```
[{
"id": "model-apps",
"type": "LightwoodAppModel"
},
{
"id": "window",
"type": "LightwoodWindow",
"children": [
{
"id": "list-apps",
"type": "LightwoodList",
"model": "model-apps",
"signals": [
{ "name": "activated", "handler": "app_activated_cb" }
]
}
]
}]
```
The differences of the B-variant application chooser in comparison to
the A-variant application chooser are:
1. There is no categories model and no categories roller.
2. There is no more box inside the main window widget.
3. The list widget is a `LightwoodList` instead of a `LightwoodRoller`.
This is a visual difference dictated by the widget implementation
and chosen for this variant, but the data backend for both lists (in
the model `model-apps`) is unchanged. Both widgets should implement a
common `LightwoodCollection` interface.
These are just two examples of how an application chooser could be
designed. The user interface files contain minimal theming as that is
achieved in separate CSS files (see [Theming]).
Typically, applications will come with many templates for system
integrators to either use, or take guidance from.
### Properties, Signals, and Callbacks
The GObject properties that can be set, the signals that can be
......@@ -751,127 +639,11 @@ and the callback signature changes, recompilation will be necessary. The
signals emitted by widgets and their type signatures are defined in
their interfaces, documented in the API documentation.
For example, in the examples above, a `LightwoodRoller` widget for listing
applications was changed to a `LightwoodList` and the activated signal
remained connected to the same `app_activated_cb` callback.
### Template Inheritance
At the time of writing, ClutterScript has no way of referring to objects
from other JSON files or of making an object a modified version of
another. A proposal to modify `ClutterScriptParser` to support this
feature is as follows (this change would take a couple of days to
implement):
An `app-switcher.json` file contains the following objects defined:
```
[{
"id": "view-header",
"type": "LightwoodHeader",
"height": 100,
"width": 200
},
{
"id": "view-footer",
"type": "LightwoodFooter",
"height": 80
},
{
"id": "app-switcher",
"type": "LightwoodWindow",
"color": "#ff0000",
"children": [ "view-header", … , "view-footer" ]
}]
```
Header and footer objects are defined (`view-header` and `view-footer`) each
with `height` properties (100 and 80 respectively). An `app-switcher` object
is also created with the `color` and `children` properties set. Note that
the `children` property is a list referring to the header and footer
objects created before. (The ellipsis between said objects marks the omission of
other objects between header and footer for brevity.)
If a system integrator wanted to give the same appearance to their app
switcher view but wanted to change the height of the header and the
colour of the main app switcher, without copy & pasting a lot of text to
redefine all these objects, objects can simply extend on previous
definitions. For example, in a `my-app-switcher.json`:
```
[{
"external-uri": "file:///path/to/app-switcher.json",
"id": "view-header",
"height": 120
},
{
"external-uri": "file:///path/to/app-switcher.json",
"id": "app-switcher",
"color": "#ffff00"
}]
```
Referencing objects defined in other files can be achieved by specifying
the `external-uri` property pointing to the other file, and the `id`
property for selecting the external object.
In this example, the `view-header `object is extended and the `height`
property is set to 120. All other properties on the original object
remain untouched. For example, the `width` property of the header remains
at 200.
It is possible to simply to refer to objects in other files without any
changes. Each external object must be referred to separately as they are
not automatically brought into scope after the first `external-uri`
reference. For example:
```
{
"id": "example-with-children",
...
"children": [
"first-child",
{
"id": "second-child",
"type": "ExampleType"
},
{
"external-uri": "file:///path/to/another.json",
"id": "third-child"
}
]
}
```
In this example, this object has three children:
1. An object called `first-child`, defined elsewhere in the JSON file.
2. An object called `second-child`, defined inline of type `ExampleType`.
3. An object called `third-child`, defined in `another.json`.
In these examples, the `external-uri` used the `file://` URI scheme, but
others supported by GIO can be used. For example, templates in
[GResources] can be used using the `resource://` URI
scheme.
Application authors should not use templates and inheritance excessively
such that every single object is in a separate file. This will cause
more disk activity and could potentially slow down the application.
Templates should be broken up when clarity is in question or when a
non-trivial object is to be used across in other views.
### Widget Factories
If a system integrator wants to replace a widget everywhere across the
user interface, they can use a widget factory to replace all instances
of said old widget with the new customised one. This is achieved by
using a factory which overrides the type parameter in a ClutterScript
object.
of said old widget with the new customised one.
For example, if a system integrator wants to stop using `LightwoodButtons`
and instead use the custom `FancyButton` class, there are no changes
......@@ -885,9 +657,9 @@ that explicitly in the template.
### Custom Widgets
Apertis widgets can be subclassed by system integrators in variants and
Widgets can be subclassed by system integrators in variants and
used by application developers by creating shared libraries linking to
the Apertis widget library. Applications then link to said new library
the widget library. Applications then link to said new library
and once the new widgets are registered with the GObject type system
they can be referred to in ClutterScript user interface files. If a
system integrator wants a radically different widget, they can write
......@@ -895,7 +667,7 @@ something from scratch, ensuring to implement the appropriate interface.
Subclassing existing widgets is for convenience but not technically
necessary.
Apertis widgets should be as modularised as possible, splitting
Widgets should be as modularised as possible, splitting
functionality into virtual methods where a system integrator might want
to override it. For example, if a system integrator wants the roller
widget to have a different activation animation depending on the number
......@@ -903,78 +675,6 @@ of items in the model, they could create a roller widget subclass, and
override the appropriate virtual methods (in this case activate) and
update the animation as appropriate:
```C
G_DEFINE_TYPE (MyRoller, my_roller, LIGHTWOOD_TYPE_ROLLER)
static void
my_roller_init (MyRoller *self)
{
}
static gboolean
my_roller_activate (MyRoller *self,
gint item_id)
{
LightwoodRoller *roller;
LightwoodRollerClass *roller_class;
LightwoodModel *model;
roller = LIGHTWOOD_ROLLER (self);
roller_class = LIGHTWOOD_ROLLER_GET_CLASS (roller);
model = lightwood_roller_get_model (roller);
if (lightwood_model_get_n_items (model) > 5) {
/* change animation */
} else {
/* reset animation */
}
/* chain up */
return roller_class->activate (roller, item_id);
}
static void
my_roller_class_init (MyRollerClass *klass)
{
LightwoodRollerClass *roller_class = LIGHTWOOD_ROLLER_CLASS (klass);
roller_class->activate = my_roller_activate;
}
```
Another example is if the system integrator wants to change another part
of the roller when scrolling starts, the appropriate signal can be
connected to:
```
G_DEFINE_TYPE (MyRoller, my_roller, LIGHTWOOD_TYPE_ROLLER)
static void
my_roller_scrolling_started (MyRoller *self,
gpointer user_data)
{
/* scrolling has started here */
}
static void
my_roller_constructed (GObject *obj)
{
/* chain up */
G_OBJECT_GET_CLASS (obj)->constructed (obj);
g_signal_connect (obj, "scrolling-started", G_CALLBACK (my_roller_scrolling_started), NULL);
}
static void
my_roller_class_init (MyRollerClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->constructed = my_roller_constructed;
}
```
In the template, this variant would stop referring to `LightwoodRoller`
and instead would use `MyRoller`, or update the widget factory entry (see
[Widget factories]).
## Models
Data that is to be displayed to the user in list widgets should be
......@@ -1006,17 +706,6 @@ by variant rules, and can be overridden by application rules, where
necessary). This is ideal for Apertis where themes set defaults and
variants need only make changes where necessary.
### Clutter Widgets
The `GtkApertisStylable` interface is a mixin for any GObject to enable
use of a `GtkStyleContext`. It is an Apertis-specific interface and
therefore not candidate for upstreaming, and will need maintaining as
the CSS machinery in GTK+ changes over time.
Every existing Clutter widget will have to be manually taught to use the
style context and any special requirements will also need to be applied
from the style context as necessary.
### Theme Changes
Applications should listen to a documented [GSettings] key for
......@@ -1056,17 +745,6 @@ should make the animation smooth.
A central GSettings key should be read to know when the system is in day
or night mode. It will be modifiable for testing and in development.
## View Management
At the time of writing, Clutter does not have a built-in view management
system. GTK+ has [GtkStack] for managing views and displaying
animations when moving between one view and another. The useful parts
of `GtkStack` could be migrated to Clutter (subject to suitable licensing)
to re-use the functionality and user testing and not waste effort in
reimplementing everything from scratch. Existing view management systems
(for example, in `libthornbury`) should also be considered for this
migration task.
## Speed Lock
There should be a system-operated service that determines when the
......@@ -1130,147 +808,9 @@ to interact with them.
This behaviour should be customisable and possibly only enabled in a
region in which laws are very strict about speed lock restrictions.
# References
## GTK+ Migration
In an older version of this document it was posed that a move from
Clutter to GTK+ might be wise. It has been decided that for the time
being a move is unwise due to the immature nature of the GTK+ Scene
Graph Kit.
The following sections were removed from the previous sections in this
document and have been left here for future reference.
### GTK+ or Clutter
The following suggestions are possible using either the GTK+ or Clutter
libraries. Existing code is currently written in Clutter, but a move to
GTK+ could be wise because GTK+ is still highly used and maintained,
whereas Clutter is less used and less maintained. The maintainers of
Clutter have even announced that planned future additions to GTK+ would
[deprecate Clutter]. Although not in stone, the deprecation is
[planned][GTK-roadmap] for the 3.20 release of GTK+ which is planned in
March 2016.
It is worth noting that Clutter widgets can be embedded inside GTK+
applications, and GTK+ widgets can be embedded inside Clutter
applications, but there are many problems with input and ensuring GTK+
functions are only called from GTK+ callbacks, so following this path is
likely not worth the eventual problems.
For completeness, the following sections with toolkit-specific
approaches are split into two such that both GTK+ and Clutter paths can
be considered.
### Specification in GtkBuilder
GtkBuilder is a method for creating user interfaces from XML files. An
example is shown below which describes the A-variant application chooser
user interface:
```
<interface>
<object class="LightwoodAppCategoryModel" id="model_categories" />
<object class="LightwoodAppModel" id="model_apps" />
<object class="LightwoodWindow" id="window">
<child>
<object class="GtkBox" id="hbox1">
<property name="homogeneous">True</property>
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
<child>
<object class="LightwoodRoller" id="roller_categories">
<property name="model">model_categories</property>
<property name="app-list">roller_apps</property>
<signal name="activated" handler="category_activated_cb" />
</object>
</child>
<child>
<object class="LightwoodRoller" id="roller_apps">
<property name="model">model_apps</property>
<signal name="activated" handler="app_activated_cb" />
</object>
</child>
</object>
</child>
</object>
</interface>
```
The first two objects created (`model_categories` and `model_apps`) are
models for the application categories available on the system, and the
applications available on the system—due to their class names
(`LightwoodAppCategoryModel` and `LightwoodAppModel` respectively). These
models are not widgets visible in the user interface, but proper widgets
will refer to them later in the template.
The next entry describes the main window in the user interface, inside
of which there is a horizontal box (with some style properties set),
with two children that are both of type `LightwoodRoller`. Although these
widgets are of the same type, they are different instances and they have
been given different models. The first roller widget (on the left) has
been given the `model_categories` model and the second roller widget (on
the right) has been given the `model_apps` model, both created at the
beginning of the XML file.
Additionally the `LightwoodRoller::activated` signal is connected on
both rollers to different callbacks. The signal callback names are
listed in the application documentation. In this case, when the
left-hand roller with categories is changed (activated), the right-hand
roller with applications is updated (set by the `app-list` property).
Another example to compare is given below with the B-variant application
chooser user interface:
```
<interface>
<object class="LightwoodAppModel" id="model_apps" />
<object class="LightwoodWindow" id="window">
<child>
<object class="LightwoodList" id="list_apps">
<property name="model">model_apps</property>
<signal name="activated" handler="app_activated_cb" />
</object>
</child>
</object>
</interface>
```
The differences of the B-variant application chooser in comparison to
the A-variant application chooser are:
1. There is no categories model and no categories roller.
2. There is no more box inside the main window widget.
3. The list widget is a `LightwoodList` instead of a `LightwoodRoller`.
This is a visual difference dictated by the widget implementation
and chosen for this variant, but the data backend for both lists (in
the model `model_apps`) is unchanged.
These are just two examples of how an application chooser could be
designed. The user interface files contain minimal theming as that is
achieved in separate CSS files (see [Theming]: {{< ref "#theming" >}} )).
Typically, applications will come with many templates for system
integrators to either use, or take guidance from.
### GTK+ Widgets
Support for `GtkStyleContext` inside GTK+ widgets is already present.
Widgets inside the GTK+ library (and therefore also their subclasses)
already talk to the style context and are drawn according to custom
styling.
New GTK+ widgets with special requirements would need to get the
appropriate style information from the style context and apply it as
necessary. This is documented in the GTK+ documentation and it is easy
to find examples of it in the source code.
[Adwaita]: https://git.gnome.org/browse/gtk+/tree/gtk/theme/Adwaita
[Animations]: {{< ref "#animations" >}}
[Appearance customisation]: {{< ref "#appearance-customisation" >}}
[ClutterScript]: https://developer.gnome.org/clutter/stable/ClutterScript.html#ClutterScript.description
[CSS styling]: {{< ref "#css-styling" >}}
[CSS transitions]: http://www.w3schools.com/css/css3_animations.asp
[Custom widgets]: {{< ref "#custom-widgets" >}}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment