O Javascript nasceu com o intuito de ser uma tecnologia para interfaces web. Com o tempo, a Apple e a Google contribuíram com a sua expansão para atuação em outras camadas da aplicação. Goste você ou não, é possível criar aplicações Desktop, Mobile e servidores HTTP com a linguagem. Muito disso foi possível graças ao interpretador V8 e melhorias estruturais na linguagem que nasceu muito pobre e mal estruturada, mas, com o tempo, virou uma boa opção até para os que gostam de uma linguagem mais verbosa e estrita, pois podem fazer o uso dela com o TypeScript.
Todavia, as soluções Javascript para interfaces gráficas ainda estão muito presas a interfaces web, fazendo com que grande parte das aplicações Desktop e mobile atualmente sejam um navegador disfarçado de janela. Como qualquer tecnologia, isso tem seus trade-offs. Este tipo de solução costuma ser menos performática, ter instaladores mais inchados e não performam bem em computadores lowend como Raspberry Pis ou notebooks com pouca memória RAM (artefato muito valorizado nos dias atuais). Foi para resolver este problema que eu criei uma simples biblioteca para renderizar telas utilizando outro approach.
Há alguns anos atrás, eu passei a conhecer uma biblioteca C para desenvolvimento de jogos e aplicações gráficas muito interessante e a que mais me chamou a atenção nos últimos anos. Essa biblioteca é a Raylib. Lançada em novembro de 2013 por Ramon Santamaria (raysan5), a raylib é open-source e leve: windowing, render 2D/3D via OpenGL, texturas, fontes, áudio, input e suporte multiplataforma (desktop, web, Android, Raspberry Pi, entre outros), com uma API simples pensada para quem está começando em game/dev gráfico. Em cima da raylib, foi criada a RayGui, outra biblioteca em C para desenvolver interfaces gráficas renderizando diretamente via GPU por OpenGL, utilizando a Raylib.
A minha ideia foi a seguinte: Criar um wrapper da RayGui e de algumas rotinas da Raylib em Javascript, no runtime NodeJS, a princípio. Quando houver suporte a Android, por exemplo, outro runtime será utilizado. Além de criar suporte para chamar as rotinas RayGui em Javascript, também criar estruturas de dados comumente utilizadas em Frontends para a criação de telas.
A parte do wrapper das linguagens e os trabalhos mais repetitivos foram feitos por vibecoding. O resto das bibliotecas e exemplos foram codificados manualmente e/ou utilizando prompts direcionados. A LLM utilizada foi o Grok 4.5.
A solução se transformou em algo parecido com um "React Native para Desktop". A lib criada se chama DebloatUi. Está disponível no seguinte repositório: https://github.com/misabitencourt/debloat-ui.
As interfaces gráficas podem ser criadas utilizando immediate mode, ou seja, como toda a interface é um loop infinito, as telas podem ser programadas no nível desse loop. Trabalhar de um modo mais abstrato utilizando componentes e programação reativa também é possível. O diretório ./examples possui os seguintes exemplos:
-
basic.js— immediate mode básico (callback por frame) -
counter.js— estado, botões, slider, textbox e checkbox no modo low-level -
reactive-app.js— app reativo comapp/view/dispatch -
reactive-components.js— componentes como funções (padrão React-like) -
reactive-layout.js— grid/layout helpers -
reactive-scroll.js— painel com scroll vertical -
reactive-image.js— texturas/imagens -
reactive-sprite.js— sprites / regiões de atlas -
reactive-multiline.js— texto multilinha -
reactive-table.js— tabela -
reactive-icons.js/reactive-icon-grid.js— ícones e grade de ícones -
reactive-theme.js— temas/estilos raygui -
reactive-keys.js/reactive-mouse.js/reactive-drag-text.js— teclado, mouse e drag -
game.js— exemplo de jogo -
ts/layout.ts— layout em TypeScript
Segue um exemplo de código, veja como é simples:
const {
app,
h,
label,
button,
panel,
textBlock,
statusBar,
} = require('debloatui/reactive');
// Componente: painel + texto + três botões
function ActionCard({ x, y, w, height, title, text, onPrimary, onSecondary, onTertiary }) {
const pad = 14;
const btnW = Math.floor((w - pad * 2 - 8 * 2) / 3);
const btnH = 30;
const btnY = y + height - pad - btnH;
return h(
panel({ x, y, w, h: height, text: title }),
textBlock({
x: x + pad,
y: y + 36,
w: w - pad * 2,
h: height - 36 - btnH - pad * 2,
wrap: 'word',
text,
}),
button({ x: x + pad, y: btnY, w: btnW, h: btnH, text: 'OK', onclick: onPrimary }),
button({ x: x + pad + btnW + 8, y: btnY, w: btnW, h: btnH, text: 'Info', onclick: onSecondary }),
button({ x: x + pad + (btnW + 8) * 2, y: btnY, w: btnW, h: btnH, text: 'Close', onclick: onTertiary })
);
}
const setLast = (message) => (s) => ({ ...s, lastAction: message });
app({
init: { lastAction: 'Click a button on any card…' },
window: { title: 'debloatui — component cards', width: 920, height: 420, fps: 60 },
view: (s) =>
h(
label({ x: 24, y: 16, w: 872, h: 24, text: 'Componentization · function components' }),
label({ x: 24, y: 40, w: 872, h: 22, text: `Last action: ${s.lastAction}` }),
ActionCard({
x: 24,
y: 72,
w: 280,
height: 260,
title: 'Backups',
text: 'Create a local backup of your project files.',
onPrimary: setLast('Backups · OK'),
onSecondary: setLast('Backups · Info'),
onTertiary: setLast('Backups · Close'),
}),
statusBar({
x: 0,
y: 400,
w: 920,
h: 20,
text: 'ActionCard({ text, onPrimary, ... }) · examples/reactive-components.js',
})
),
});


Top comments (0)