The C library
for user interfaces
An open source UI toolkit for building cross-platform desktop apps.
What's in LCUI?
The libraries you need to make the UI.
Portable UI library
UI library has few dependencies and no system API dependencies.
Small Graphics Library
Provide basic graphics processing capabilities for UI rendering.
Platform APIs
Provide platform related APIs, such as window management, message loop, clipboard, etc.
Router
define routes, map them to widgets, and display the appropriate widget based on the URL.
I18n
Configure translated text in multiple languages and freely switch languages at runtime.
Window Mapping
Map widget to the system window so that its content can be synchronized to the window.
Preset Widgets
Text, TextEdit, Button, ScrollBar, etc.
CSS Support
Parse CSS, select styles, and calculate styles.
XML Support
Declare User Interface with XML.
Create user interfaces from widgets
LCUI lets you build user interfaces out of individual pieces called widgets. Create your own LCUI widgets and combine them into entire screens, pages, and apps.
- todolist.xml
- todolist.css
- todolist.c
<?xml version="1.0" encoding="UTF-8" ?>
<lcui-app>
<resource type="text/css" src="todolist.css"/>
<ui>
<w class="task-item is-completed">
<w class="task-status" />
<text class="task-name">Download LCUI source code</text>
<w class="task-delete" />
</w>
<w class="task-item is-completed">
<w class="task-status" />
<text class="task-name">Build LCUI</text>
<w class="task-delete" />
</w>
<w class="task-item">
<w class="task-status" />
<text class="task-name">Read LCUI tutorials</text>
<w class="task-delete" />
</w>
<w class="task-item">
<w class="task-status" />
<text class="task-name">Create my LCUI application</text>
<w class="task-delete" />
</w>
</ui>
</lcui-app>
.task-item:last-child {
border-bottom: 0;
}
.task-item:hover {
background-color: #f6fbff;
}
.task-item.is-completed {
background-color: rgba(74, 206, 163, 0.1);
}
.task-status {
width: 18px;
height: 18px;
cursor: pointer;
border: 2px solid #bbbdc7;
border-radius: 9px;
background-color: #fff;
background-image: url("check.png");
background-size: 100%;
margin-right: 10px;
flex: none;
text-align: center;
}
.task-item.is-completed .task-status {
color: #fff;
background-color: #4acea3;
border-color: #38bb90;
}
.task-name {
flex: 1;
}
.task-delete {
width: 18px;
height: 18px;
display: none;
flex: none;
background-size: 100%;
background-image: url("delete.png");
}
.task-item:hover .task-delete {
display: block;
}
#include <LCUI.h>
#include <LCUI/main.h>
int main(int argc, char **argv)
{
ui_widget_t *pack;
lcui_init();
pack = ui_load_xml_file("todolist.xml");
if (!pack) {
return -1;
}
ui_root_append(pack);
ui_widget_unwrap(pack);
ui_widget_set_title(ui_root(), L"Todo list");
return lcui_main();
}
Todo List
Download LCUI source code
Build LCUI
Read LCUI tutorials
Create my LCUI application
Add interactivity wherever you need it
The widgets in LCUI are event driven. You can add event handlers to widgets in response to interactions, and then make some content or style changes in the event handlers.
- todolist.xml
- todolist.css
- todolist.c
<?xml version="1.0" encoding="UTF-8" ?>
<lcui-app>
<resource type="text/css" src="todolist.css"/>
<ui>
<w class="app">
<w class="header">
<text class="title">Todo list</text>
<w class="tools">
<text id="count" class="count" />
<w id="filters" class="task-filters">
<text class="task-filter" data-value="all">All</text>
<text class="task-filter" data-value="active">Active</text>
<text class="task-filter" data-value="completed">Completed</text>
</w>
</w>
</w>
<textedit id="input" class="task-input" placeholder="Add a new task..." />
<w id="list" class="task-list" />
</w>
</ui>
</lcui-app>
root {
display: flex;
align-items: center;
justify-content: center;
background-color: #adcdfc;
}
* {
box-sizing: border-box;
}
.app {
max-width: 400px;
width: 100%;
background-color: #fff;
border-radius: 16px;
box-shadow: 0 20px 80px rgba(0, 0, 0, 0.3);
}
text {
font-size: 14px;
color: #455963;
}
.title {
font-size: 20px;
font-weight: 600;
padding: 20px 20px 6px 20px;
}
.tools {
display: flex;
align-items: center;
margin-bottom: 10px;
padding: 0 20px;
}
.count {
color: #8a9ca5;
display: inline-block;
}
.task-filters {
margin-left: auto;
display: inline-block;
}
.task-input {
color: #455963;
padding: 10px 20px;
border-top-width: 0;
border-left-width: 0;
border-right-width: 0;
width: 100%;
}
.task-input:focus {
box-shadow: none;
border-top-width: 0;
border-left-width: 0;
border-right-width: 0;
}
.task-filter {
padding: 3px 8px;
color: #8a9ca5;
border-radius: 10px;
display: inline-block;
}
.task-filter:hover {
background-color: rgba(121, 150, 165, 0.1);
}
.task-filter.is-active {
background-color: #7996a5;
color: #fff;
}
.task-item {
display: flex;
align-items: center;
padding: 12px 20px;
border-top: 1px solid #eef0f5;
}
.task-item:last-child {
border-bottom: 0;
}
.task-item:hover {
background-color: #f6fbff;
}
.task-item.is-completed {
background-color: rgba(74, 206, 163, 0.1);
}
.task-status {
width: 18px;
height: 18px;
cursor: pointer;
border: 2px solid #bbbdc7;
border-radius: 9px;
background-color: #fff;
background-image: url("check.png");
background-size: 100%;
margin-right: 10px;
flex: none;
text-align: center;
}
.task-item.is-completed .task-status {
color: #fff;
background-color: #4acea3;
border-color: #38bb90;
}
.task-name {
flex: 1;
}
.task-delete {
width: 18px;
height: 18px;
display: none;
flex: none;
background-size: 100%;
background-image: url("delete.png");
}
.task-item:hover .task-delete {
display: block;
}
#include <stdio.h>
#include <LCUI.h>
#include <LCUI/main.h>
typedef struct task_t {
unsigned id;
wchar_t *name;
const char *status;
} task_t;
struct todolist_app_t {
unsigned id;
list_t tasks;
} app = { 0 };
ui_widget_t *ui_task_item_create(task_t *task)
{
char id[32] = { 0 };
ui_widget_t *item = ui_create_widget(NULL);
ui_widget_t *status = ui_create_widget(NULL);
ui_widget_t *del = ui_create_widget(NULL);
ui_widget_t *name = ui_create_widget("text");
snprintf(id, 32, "%u", task->id);
ui_text_set_content_w(name, task->name);
ui_widget_set_attribute(item, "data-id", id);
ui_widget_add_class(item, "task-item");
if (strcmp(task->status, "completed") == 0) {
ui_widget_add_class(item, "is-completed");
}
ui_widget_add_class(status, "task-status");
ui_widget_add_class(del, "task-delete");
ui_widget_append(item, status);
ui_widget_append(item, name);
ui_widget_append(item, del);
return item;
}
void ui_todolist_update_count(void)
{
wchar_t text[32];
ui_widget_t *count = ui_get_widget("count");
swprintf(text, 32, app.tasks.length > 1 ? L"%u tasks" : L"%u task",
app.tasks.length);
ui_text_set_content_w(count, text);
}
void update_filter_status(ui_widget_t *w, void *activeStatus)
{
const char *status = ui_widget_get_attr_val(w, "data-value");
if (status && strcmp(status, activeStatus) == 0) {
ui_widget_add_class(w, "is-active");
} else {
ui_widget_remove_class(w, "is-active");
}
}
void ui_todolist_filter(const char *status)
{
task_t *task;
list_node_t *node;
ui_widget_t *list = ui_get_widget("list");
ui_widget_empty(list);
for (list_each(node, &app.tasks)) {
task = node->data;
if (strcmp(status, "all") != 0 &&
strcmp(task->status, status) != 0) {
continue;
}
ui_widget_append(list, ui_task_item_create(task));
}
ui_widget_each(ui_get_widget("filters"), update_filter_status,
(void *)status);
ui_todolist_update_count();
}
void ui_todolist_add(const wchar_t *name, const char *status)
{
task_t *task = malloc(sizeof(task_t));
task->id = ++app.id;
task->name = wcsdup2(name);
task->status = status ? status : "active";
list_append(&app.tasks, task);
ui_widget_append(ui_get_widget("list"), ui_task_item_create(task));
ui_todolist_update_count();
}
void on_input_keydown(ui_widget_t *w, ui_event_t *e, void *arg)
{
wchar_t name[256];
if (e->key.code == KEY_ENTER) {
ui_textedit_get_text_w(w, 0, 255, name);
ui_todolist_add(name, "active");
ui_textedit_clear_text(w);
}
}
void on_filter_click(ui_widget_t *w, ui_event_t *e, void *arg)
{
const char *status = ui_widget_get_attr_val(e->target, "data-value");
if (status) {
ui_todolist_filter(status);
}
}
void on_task_list_click(ui_widget_t *w, ui_event_t *e, void *arg)
{
const char *id_str;
unsigned id;
task_t *task;
list_node_t *node;
ui_widget_t *item = e->target->parent;
for (item = e->target; !ui_widget_has_class(item, "task-item");
item = item->parent)
;
id_str = ui_widget_get_attr_val(item, "data-id");
if (!id_str || sscanf(id_str, "%u", &id) != 1) {
return;
}
if (ui_widget_has_class(e->target, "task-delete")) {
ui_widget_remove(item);
for (list_each(node, &app.tasks)) {
task = node->data;
if (task->id == id) {
list_delete_node(&app.tasks, node);
break;
}
}
ui_todolist_update_count();
return;
}
if (!ui_widget_has_class(e->target, "task-status")) {
return;
}
for (list_each(node, &app.tasks)) {
task = node->data;
if (task->id != id) {
continue;
}
if (strcmp(task->status, "completed") == 0) {
task->status = "active";
ui_widget_remove_class(item, "is-completed");
break;
}
task->status = "completed";
ui_widget_add_class(item, "is-completed");
break;
}
}
void ui_todolist_init(void)
{
ui_widget_on(ui_get_widget("input"), "keydown", on_input_keydown, NULL);
ui_widget_on(ui_get_widget("filters"), "click", on_filter_click, NULL);
ui_widget_on(ui_get_widget("list"), "click", on_task_list_click, NULL);
ui_todolist_filter("all");
}
int main(int argc, char **argv)
{
ui_widget_t *pack;
lcui_init();
pack = ui_load_xml_file("todolist.xml");
if (!pack) {
return -1;
}
ui_root_append(pack);
ui_widget_unwrap(pack);
ui_widget_set_title(ui_root(), L"Todo list");
ui_todolist_init();
ui_todolist_add(L"Download LCUI source code", "completed");
ui_todolist_add(L"Build LCUI", "completed");
ui_todolist_add(L"Read LCUI tutorials", "active");
ui_todolist_add(L"Create my LCUI application", "active");
return lcui_main();
}
Todo list
Todo list
4 tasks
all
active
completed
Download LCUI source code
Build LCUI
Read LCUI tutorials
Create my LCUI application