diff --git a/README.md b/README.md
index 6d6989d..6f58d3b 100644
--- a/README.md
+++ b/README.md
@@ -18,6 +18,10 @@ The type of script is determined by the line (meta tag) at the beginning of the
# Changelog
+### 5.2.1
+
+* Added support for complex UIs
+
### 5.2.0
* Added `widgets` module to app widgets interaction
@@ -349,8 +353,7 @@ end
## Application management
* `apps:apps([sort_by])` - returns the table of tables of all installed applications;
-* `apps:app(package_name)` - return the table of tables of the given application;
-, `sort_by` - sort option (see below);
+* `apps:app(package_name)` - return the table of the given application;
* `apps:launch(package)` - launches the application;
* `apps:show_edit_dialog(package)` - shows edit dialog of the application;
* `apps:categories()` - returns a table of category tables.
@@ -580,6 +583,12 @@ _Avaialble from: 4.1.3_
All files are created in the subdirectory `/sdcard/Android/data/ru.execbit.aiolauncher/files/scripts` without ability to create subdirectories.
+## Rich UI
+
+Starting with version 5.2.1, AIO Launcher includes an API that allows for displaying a more complex interface than what the high-level functions of the `ui` module allowed. For example, you can display text of any size, center it, move it up and down, display buttons on the left and right sides of the screen, draw icons of different sizes, and much more. Essentially, you can replicate the appearance of any built-in AIO widget.
+
+[Detailed instructions](README_RICH_UI.md)
+
## App widgets
Starting from version 5.2.0, AIO Launcher supports interaction with app widgets through scripts. This means that you can create a wrapper for any app's widget that will fully match the appearance and style of AIO. You can also use this API if you need to retrieve information from other applications, such as the balance on your mobile phone account or your car's parking spot. If the application has a widget providing such information, you will be able to access it.
diff --git a/README_RICH_UI.md b/README_RICH_UI.md
new file mode 100644
index 0000000..e144ae8
--- /dev/null
+++ b/README_RICH_UI.md
@@ -0,0 +1,81 @@
+Starting with version 5.2.1, AIO Launcher includes an API that allows for displaying a more complex interface than what the high-level functions of the `ui` module allowed. For example, you can display text of any size, center it, move it up and down, display buttons on the left and right sides of the screen, draw icons of different sizes, and much more. Essentially, you can replicate the appearance of any built-in AIO widget.
+
+Open the example [samples/rich-gui-basic-sample.lua] and study it. As you can see, the new API consists of just one function `gui`, which takes a table describing the UI as input and returns an object that has a `render()` method for drawing this UI.
+
+The UI is built line by line, using commands that add elements from left to right, with the possibility of moving to a new line. The provided example displays two lines, under which are two buttons:
+
+```
+{"text", "First line"},
+{"new_line", 1},
+{"text", "Second line"},
+{"new_line", 2},
+{"button", "Button #1"},
+{"spacer", 2},
+{"button", "Button #2"},
+```
+
+The first command displays the line "First line", the "new_line" command moves to a new line, adding a space equal to one unit (each unit is 4 pixels). Then, the "text" command adds a new line, followed by a move to a new line and the display of two buttons. Since there is no "new_line" command between the button display commands, they will be displayed in one line, one after the other, from left to right with a gap of 2 units. The "spacer" command is responsible for the horizontal gap.
+
+This example is already useful, but it's too plain. Let's add some colors and vary the text lines a bit: make the first line larger, and write the second line in italic font. Also, let's work on the color of the buttons:
+
+```
+{"text", "First line", {size = 21}},
+{"new_line", 1},
+{"text", "Second line"},
+{"new_line", 2},
+{"button", "Button #1", {color = "#ff0000"}},
+{"spacer", 2},
+{"button", "Button #2", {color = "#0000ff"}},
+```
+
+To change the size of a line, we used the table `{size = 21}`. This is the standard approach if a command has more than one parameter, then all other arguments are passed in the table. To make the text italic, we used the HTML tag ``. The "text" command supports many tags for text transformation. The color of the buttons is also changed using a table.
+
+It's more interesting now, but again, an interface where all elements are grouped on the left side may not always be suitable. We need a way to align elements on the right side of the screen or in the center. Let's make the first line be in the middle of the line (kind of like a title), and the second button - on the right side:
+
+```
+{"text", "First line", {size = 21, gravity = "center_h"}},
+{"new_line", 1},
+{"text", "Second line"},
+{"new_line", 2},
+{"button", "Button #1", {color = "#ff0000"}},
+{"spacer", 2},
+{"button", "Button #2", {color = "#0000ff", gravity = "right"}},
+```
+
+Here we used the `gravity` parameter to change the location of the element in the line. Three things are important to know about `gravity`:
+
+1. It changes the position of the element only within the current line;
+2. Possible `gravity` values: `left`, `top`, `right`, `bottom`, `center_h` (horizontal centering), `center_v` (vertical centering);
+3. `Gravity` values can be combined, for example, to display a text element in the top right corner of the current line, you can specify `gravity = "top|right"`;
+
+Also, there are two limitations to know about:
+
+1. The `center_h` value is applied to each element separately, meaning if you add two lines with the gravity value `center_h` in one line, they will not both be grouped and displayed in the center, but instead, they will split the screen in half and be displayed each in the center of its half;
+2. The `right` value affects not only the element to which it is applied but also all subsequent elements in the current line. That means if you add another button after "Button #2", it will also be on the right side after "Button #2".
+
+Surely you will want to add icons to your UI. There are several ways to do this. The first way is to embed the icon directly into the text, in this case, you can use any icon from the FontAwesome set:
+
+```
+{"text", "This is icon: %%fa:microphone%%"}
+```
+
+The second way: use the icon command (in this case, you can also specify the size and color of the icon):
+
+```
+{"icon", "fa:microphone", {size = 32, color = "#ff0000"}}
+```
+
+The second method also allows displaying icons of applications and contacts. How to do this is shown in the example [samples/rich-ui-sample.lua].
+
+Handling clicks on elements works the same as when using the `ui` module API. Just define `on_click()` or `on_long_click()` functions. The first parameter of the function will be the index of the element. How to use this mechanism can be learned in the example [samples/rich-ui-sample.lua].
+
+This is all you need to know about the new API. Below is an example demonstrating all supported elements and all their default parameters:
+
+```
+{"text", "", {size = 17, gravity = "left"}},
+{"button", "", {color = "", gravity = "left"}},
+{"icon", "", {size = 17, color = "", gravity = "left"}},
+{"progress" "", {progress = 0, color = "", gravity = "left"}},
+{"new_line", 0},
+{"spacer", 0},
+```
diff --git a/samples/rich-gui-basic-sample.lua b/samples/rich-gui-basic-sample.lua
new file mode 100644
index 0000000..05dcb6d
--- /dev/null
+++ b/samples/rich-gui-basic-sample.lua
@@ -0,0 +1,16 @@
+-- name = "Rich GUI Basic sample"
+
+function on_resume()
+ my_gui = gui{
+ {"text", "First line"},
+ {"new_line", 1},
+ {"text", "Second line"},
+ {"new_line", 2},
+ {"button", "Button #1"},
+ {"spacer", 2},
+ {"button", "Button #2"},
+ }
+
+ my_gui.render()
+end
+
diff --git a/samples/rich-gui-sample.lua b/samples/rich-gui-sample.lua
new file mode 100644
index 0000000..e7ee715
--- /dev/null
+++ b/samples/rich-gui-sample.lua
@@ -0,0 +1,67 @@
+-- name = "Rich GUI sample"
+
+function on_resume()
+ local app = apps:app("ru.execbit.aiolauncher")
+ if app == nil then return end
+
+ my_gui = gui{
+ {"text", "Title", {size = 19, gravity = "center_h"}},
+ {"new_line", 2},
+ {"text", "Hello, World", {size = 21}},
+ {"spacer", 2},
+ {"text", "Center small text", {size = 8, gravity = "center_v"}},
+ {"text", "Top right text", {size = 8, gravity = "top|right"}},
+ {"new_line", 1},
+ {"button", "Ok", {color = "#00aa00"}},
+ {"button", "Neutral", {color = "#666666", gravity = "right"}},
+ {"spacer", 2},
+ {"button", "Cancel", {color = "#ff0000", gravity = "right"}},
+ {"new_line", 2},
+ {"progress", "Progress #1", {progress = 70}},
+ {"progress", "Progress #2", {progress = 30, color = "#0000ff"}},
+ {"new_line", 2},
+ {"button", "Center button", {gravity = "center_h"}},
+ {"new_line", 2},
+ {"icon", "fa:microphone", {size = 17, color = "#00ff00", gravity = "center_v"}},
+ {"spacer", 4},
+ {"icon", "fa:microphone", {size = 22, gravity = "center_v"}},
+ {"spacer", 4},
+ {"icon", "fa:microphone", {size = 27, gravity = "center_v"}},
+ {"spacer", 4},
+ {"icon", "fa:microphone", {size = 32, gravity = "center_v"}},
+ {"icon", app.icon, {size = 17, gravity = "center_v|right"}},
+ {"spacer", 4},
+ {"icon", app.icon, {size = 22, gravity = "center_v"}},
+ {"spacer", 4},
+ {"icon", app.icon, {size = 27, gravity = "center_v"}},
+ {"spacer", 4},
+ {"icon", app.icon, {size = 32, gravity = "center_v"}},
+ }
+
+ my_gui.render()
+end
+
+function on_apps_changed()
+ on_resume()
+end
+
+function on_click(idx, extra)
+ local elem_name = my_gui.ui[idx][1]
+
+ if elem_name == "text" then
+ my_gui.ui[idx][2] = ""..my_gui.ui[idx][2]..""
+ elseif elem_name == "button" then
+ my_gui.ui[idx][2] = "Clicked"
+ elseif elem_name == "progress" then
+ my_gui.ui[idx][3].progress = 100
+ elseif elem_name == "icon" then
+ my_gui.ui[idx][3].color = "#ff0000"
+ end
+
+ my_gui.render()
+ ui:show_toast("Clicked: "..my_gui.ui[idx][1])
+end
+
+function on_long_click(idx)
+ ui:show_toast("Long click: "..my_gui.ui[idx][1])
+end