Studio#
O Sulfite Studio é um editor visual Flutter para criar e editar relatórios. Arraste elementos no canvas, ajuste propriedades no inspetor, e veja o resultado em tempo real.
Executáveis pré-compilados#
O Studio é um app Flutter — roda nativamente em macOS, Windows, Linux, Android e iOS sem precisar compilar nada. Basta baixar o executável da plataforma desejada na página de releases.
| Plataforma | Formato | Requisitos |
|---|---|---|
| macOS | .app (universal) | macOS 12+ |
| Windows | .exe (installer) | Windows 10+ |
| Android | .apk | Android 5.0+ |
| Linux | AppImage / .tar.gz | Ubuntu 20.04+ |
| Web | Playground online | Navegador moderno |
Você também pode experimentar o Studio diretamente no navegador pelo Playground — sem instalar nada.
Compilar a partir do fonte#
Se preferir compilar localmente:
dependencies:
sulfite_studio:
git:
url: https://github.com/rafaelgazani/sulfite.git
path: packages/sulfite_studio
O Studio depende de sulfite_core, easy_localization, google_fonts,
lucide_icons, entre outros.
Integração#
O ponto de entrada é o widget SulfiteDesigner:
import 'package:sulfite_studio/sulfite_studio.dart';
SulfiteDesigner(
initialReport: myReportDefinition,
initialSampleData: {
'items': [
{'name': 'Produto A', 'price': 10.0}
],
}, // opcional
)
Parâmetros principais#
| Parâmetro | Tipo | Descrição |
|---|---|---|
initialReport |
ReportDefinition |
Definição inicial carregada no canvas |
initialSampleData |
Map<String, dynamic>? |
Dados de amostra para o modo Preview |
dataSourceResolvers |
List<DataSourceResolver> |
Resolvers para datasources externos (HTTP, Postgres etc.) |
onSave |
Future<void> Function(ReportDefinition)? |
Chamado ao salvar; ausente = botão salvar oculto |
lookupResolver |
LookupResolver? |
Resolver de lookups para o SulfiteFilterScreen |
reportManager |
Widget? |
Widget de biblioteca de relatórios (ver abaixo) |
scriptHandlers |
Map<String, ScriptHandler> |
Handlers de scripts registrados no engine |
Slot reportManager (Biblioteca)#
Quando reportManager é fornecido, o botão Biblioteca (ícone de pasta) aparece na toolbar. Ao clicar, o Studio empurra uma rota de tela cheia exibindo o widget passado.
O Studio não importa nenhum pacote específico — aceita qualquer Widget. Isso mantém o acoplamento zero entre
sulfite_studio e sulfite_report_manager.
import 'package:sulfite_studio/sulfite_studio.dart';
import 'package:sulfite_report_manager/sulfite_report_manager.dart';
final registry = MutableConnectionRegistry();
SulfiteDesigner(
initialReport: report,
dataSourceResolvers: [
PostgRestDataSourceResolver(registry: registry),
PostgresDataSourceResolver(registry: registry),
RestDataSourceResolver(),
],
reportManager: ReportManagerWidget(
repository: SqliteReportRepository.open(db),
canInsert: true,
canEdit: true,
canDelete: true,
canSettings: true,
viewerBuilder: (ctx, entry, definition) {
// Carregar o relatório selecionado no canvas atual
Navigator.of(ctx).pop();
viewModel.loadReport(definition);
return const SizedBox.shrink();
},
),
)
Para expor o gerenciador de conexões junto com a biblioteca:
reportManager: Column(
children: [
Expanded(
child: ReportManagerWidget(
repository: SqliteReportRepository.open(db),
viewerBuilder: (ctx, entry, def) { ... },
),
),
ConnectionManagerWidget(registry: registry),
],
)
Funcionalidades#
Tela de carregamento#
Ao iniciar, o Studio exibe o StudioSplashScreen enquanto as preferências são lidas do disco. A desserialização do JSON ocorre em um
Isolate separado (via compute()), mantendo a UI thread livre. A tela faz crossfade para o editor assim que o carregamento termina.
Canvas#
- Elementos posicionados por drag & drop
- Resize com handles nos cantos
- Grid overlay (8pt padrão, configurável)
- Snap to grid
- Zoom de 0.1x a 5.0x com pan
Toolbar#
- Adicionar elementos (Text, Field, Image, etc.)
- Desfazer / refazer (undo/redo com histórico)
- Gerenciar bands (adicionar, remover, reordenar)
- Validar relatório
- Ferramentas de multi-seleção: alinhar (esquerda/direita/topo/base/centro H/centro V) e distribuir (H/V)
- Posicionar na Página: alinha o(s) elemento(s) selecionado(s) em relação à band/página — esquerda, centro H, direita, topo, centro V, base
Ajuda contextual da toolbar
- Ao passar o mouse sobre um item da toolbar por 500ms, o Studio exibe um card de ajuda contextual
-
O card usa o padrão visual
StudioFloatingCarde traz descrição + dicas rápidas por tipo de elemento - O conteúdo é rolável e a altura é ajustada dinamicamente conforme o espaço disponível na janela
- O posicionamento é inteligente: se não houver espaço suficiente abaixo, o card é reposicionado para permanecer totalmente legível
- O card fecha no botão
Xou ao clicar no próprio item da toolbar
Modos de visualização#
- Design: canvas com elementos editáveis
- Preview: renderização em tempo real do relatório
- Code: editor JSON bidirecional — alterações no JSON atualizam o canvas e vice-versa
Editor de código (modo Code)
- Syntax highlighting com cores por tipo de token (chave, string, número, booleano, nulo)
- Busca com navegação entre resultados (
Ctrl+F) - Formatação automática com um clique
- Tooltips de propriedades ao passar o cursor sobre chaves conhecidas do schema
-
Validação em tempo real — erros de JSON e problemas semânticos (via
ReportValidator) aparecem inline com sublinhado e badge de contagem -
Sincronização bidirecional via listener: canvas → JSON (
_syncFromViewModel) e JSON → canvas (onChanged)
Gerenciador de bands#
O Studio oferece dois pontos de edição de bands:
Painel lateral (acesso rápido)
Ao expandir uma band no painel esquerdo, é possível editar inline:
| Campo | Tipos | Descrição |
|---|---|---|
| Altura | Todos | Altura em pontos (pt) |
| Fonte de dados | detail |
DataSource vinculado |
| Quebra de página | Todos | Se true, insere quebra antes da band |
| Visibilidade | header, footer |
Dropdown com BandVisibility — controla em quais páginas a band é renderizada |
| Mostrar na página | header, footer |
Número de página específico (1-based); sobrepõe visibility |
| Imprimir quando | Todos | Expressão booleana — band é ocultada quando avalia false |
BandManagerDialog (visão completa)
Aberto pelo botão Gerenciar Bands na toolbar. Permite:
-
Adicionar bands de qualquer tipo — incluindo
groupHeaderegroupFooter(com campogroupByedataSourceId) - Reordenar por drag & drop
-
Editar todos os campos da band: altura, fonte de dados, quebra de página, visibilidade,
showOnPage,printWhenegroupBy - Excluir bands (com confirmação quando há elementos)
Inspetor de propriedades#
Painel lateral para editar propriedades do elemento selecionado: posição, tamanho, binding, formato, cores, fonte.
Para elementos de texto (Text, Field, Aggregate), o inspetor também permite:
- alinhamento horizontal e vertical
padding-
borda sólida/tracejada (
hasBorder,borderColor,borderWidth,borderDashed,borderDashGap) - Posicionar na Página — alinha o elemento em relação à página: esquerda, centro horizontal, direita, topo, centro vertical, base da band
Busca e organização
- Clique na lupa (🔍) no cabeçalho do inspetor para abrir um campo de busca — filtra seções pelo nome enquanto nenhum elemento está selecionado
- O botão ⇅ colapsa ou expande todas as seções de uma vez
- As seções colapsadas são persistidas nas Preferências — o estado é preservado entre sessões
Layout responsivo
Campos relacionados são exibidos lado a lado para economizar espaço vertical:
| Campos | Layout |
|---|---|
| X / Y | Mesma linha |
| Width / Height | Mesma linha |
| Font Size / Padding | Mesma linha |
| Border Color / Border Width | Mesma linha |
| X2 / Y2 (Line) | Mesma linha |
Em painéis com menos de 360px de largura, os campos voltam à disposição vertical.
Campos de borda condicionais
Quando has_border está desativado, os campos border_color, border_width,
dashed_border e dash_gap são automaticamente ocultados, deixando o inspetor mais limpo.
Histórico de edições
Alterações em campos de texto (posição, tamanho, conteúdo) usam debounce de 600ms — o canvas atualiza em tempo real a cada tecla, mas somente uma entrada é gravada no histórico de undo/redo após o usuário parar de digitar. Campos binários (switches, cores, dropdowns) gravam imediatamente.
Seletor de cores (StudioColorPickerDialog)#
Diálogo reutilizável de seleção de cor disponível em todo o Studio:
- Roda HSV com sliders de saturação, brilho e alfa
- Campo hex com sincronização bidirecional — edite o código diretamente ou arraste a roda
- Preview da cor com animação suave
- Usado no inspetor do designer, no editor de gráficos e nas configurações de tema
final color = await StudioColorPickerDialog.show(
context,
initialColor: Colors.blue,
title: 'Cor primária',
);
Editor de gráficos#
O ChartEditorDialog permite editar as propriedades do ChartElement visualmente:
- Tipo de gráfico (barra, linha, pizza)
- Cores, legendas, grid lines e animação
- Adicionar e remover séries com poucos cliques
- Reordenar séries por drag & drop (handle de arraste por série)
- Cor de cada série editável via
StudioColorPickerDialog
Validação#
O ReportValidator verifica a definição e reporta problemas:
- Bands sem elementos
- Data sources não utilizados
- Bindings referenciando campos inexistentes
Dialog de validação
- O resultado da validação abre em um diálogo no padrão
StudioFloatingCard - O header exibe contadores por severidade (erro, warning, info)
- A lista pode ser filtrada por severidade com chips dedicados
- A interface usa transição suave (fade + slide) e ações com cantos de 8px para manter consistência visual
Script Console#
O Studio inclui um ambiente completo de edição e teste de scripts Dart embutidos no relatório.
Editor de código (aba Scripts)
O editor usa CodeForge com suporte a Dart: syntax highlighting, snippets de ScriptContext
e validação sintática em tempo real.
Layout do editor:
- Painel esquerdo — lista de scripts do relatório com drag-to-reorder, botões de adicionar e excluir, campo de busca por nome
-
Painel direito — duas abas:
- Console — logs de execução via
ctx.log(), botão Executar para testar o script com dados do Preview - Script Explorer — referência da API + IDs reais do relatório (datasources, bands, parâmetros), itens copiáveis com um tap
- Console — logs de execução via
Script Explorer
O Script Explorer exibe:
- API completa do
ScriptContext(métodos, propriedades, tipos de retorno) - IDs reais dos datasources, bands e parâmetros do relatório aberto, com indicador ● (dados disponíveis no Preview) ou ○
- Snippets prontos para copiar e inserir
O painel inicia colapsado por padrão — expanda as seções que precisar.
Console de execução
- Selecione um script e clique em Executar para rodar com o payload atual do Preview
- Saídas de
ctx.log(value)aparecem no console com timestamp - Se o datasource tiver mais linhas do que o limite de truncamento, um aviso indica quais fontes foram truncadas
- O limite de linhas por datasource é configurável nas Preferências do Studio
Testando um script
// Exemplo de script afterQuery — ordenar por região
void main() {
final items = ctx.datasource('vendas');
items.sort((a, b) => (a['regiao'] as String).compareTo(b['regiao'] as String));
ctx.setDatasource('vendas', items);
ctx.log('${items.length} registros ordenados');
}
- Abra a aba Scripts no Studio
- Selecione ou crie um script
- Certifique-se de ter dados no Preview (aba Preview deve ter sido carregada ao menos uma vez)
- Clique em Executar para ver os logs no Console
ScriptContext API
| Método | Descrição |
|---|---|
ctx.datasource(id) | Retorna a lista de registros do datasource |
ctx.setDatasource(id, rows) | Substitui os registros do datasource |
ctx.param(id) | Retorna o valor do parâmetro |
ctx.report | ReportDefinition atual |
ctx.log(value) | Escreve no Console do Studio |
Ver detalhes em Expressões.
Filtros de impressão (SulfiteFilterScreen)#
Ao clicar em Imprimir ou Salvar PDF, o Studio abre automaticamente o
SulfiteFilterScreen se o relatório tiver parâmetros definidos. O diálogo:
- Renderiza cada parâmetro com o widget correto (texto, número, data, dropdown)
-
Lookups dinâmicos — parâmetros
selectcomlookupcarregam opções via HTTP, com indicador de loading -
Cascata — quando o valor de um parâmetro-pai muda, parâmetros dependentes (
dependsOn) são recarregados automaticamente - Validação inline — regras declarativas (min, max, dateBefore, dateAfter, etc.) com mensagem de erro por campo
- Botão Confirmar só habilita quando todos os campos obrigatórios estão preenchidos e sem erros
O SulfiteFilterScreen também pode ser usado como widget standalone fora do Studio.
Ver detalhes em Filtros de impressão →.
Internacionalização (i18n)#
O Studio suporta pt-BR e en-US via easy_localization. A escolha de idioma é configurável nas Preferências e aplicada em tempo real sem reiniciar o app.
Preferências#
As preferências são persistidas localmente via PreferencesService e carregadas via PreferencesProvider
(singleton ChangeNotifier):
| Preferência | Default | Descrição |
|---|---|---|
themeMode | system | Claro, escuro ou sistema |
primaryColor |
blue |
Nome de cor pré-definida ou string hex de 6 chars (ex: 4A90E2) |
language |
pt-BR |
Idioma da interface (pt-BR ou en-US) |
showGrid | true | Exibir grid no canvas |
snapToGrid | true | Snap de elementos ao grid |
gridSize | 10.0 | Tamanho do grid em pontos |
autoSave | true | Salvar automaticamente |
splitViewWeight | 0.75 | Proporção canvas/sidebar |
Configurações de tema
A aba Tema nas Configurações oferece:
-
Idioma — SegmentedButton pt-BR / en-US com aplicação imediata via
context.setLocale() - Modo de tema — Claro / Escuro / Sistema
-
Cor primária — 8 presets animados + opção Personalizado abre
StudioColorPickerDialog; qualquer cor hex é armazenada diretamente no campoprimaryColor -
Papéis das cores — tabela informativa com os 8 tokens do Material Design (
primary,onPrimary,secondary,surface,onSurface,surfaceContainerHighest,outlineVariant,error), cada um com swatch 32×32, descrição e badge hex
Chat IA (sulfite_chat)#
O pacote sulfite_chat adiciona um agente de linguagem natural ao Studio. O usuário descreve o que quer em texto e o agente gera (ou edita) o
ReportDefinition completo, que é aplicado ao canvas imediatamente.
Provedores suportados#
| Provider | Modelos | Auth |
|---|---|---|
openai |
gpt-4.1
,
gpt-4o
,
gpt-4o-mini
,
gpt-4-turbo
,
gpt-3.5-turbo
,
o4-mini
|
apiKey |
anthropic |
claude-sonnet-4-20250514
,
claude-opus-4-20250514
,
claude-3-5-haiku-20241022
|
apiKey |
ollama |
llama3.1
,
llama3
,
mistral
,
codellama
,
qwen2.5-coder
|
baseUrl (local) |
gh_models |
openai/gpt-4.1, meta/llama-4-scout, mistral/mistral-large |
token GitHub |
gh_copilot |
copilot (ou modelo custom) |
gh auth token |
Pipeline de geração#
- Usuário escreve (texto + imagens opcionais via file picker)
- O JSON do relatório atual é injetado como contexto (
currentReport) ReportGeneratorchama o LLM via HTTP POST (com retry automático)-
A resposta é extraída do bloco
```json ```e validada contra o schema dosulfite_core - Se inválido, os erros são reenviados ao LLM para auto-correção (até 3 tentativas)
onReportGenerated(ReportDefinition)é chamado → canvas atualiza
Modos de exibição#
-
Flutuante e arrastável — painel 420×560 px, drag pela barra de título; estado preservado ao fechar (
Offstage) - Dockado — botão "fixar" move o chat para uma terceira aba no painel lateral do designer
- Configurações inline — troca de provider, model e credenciais sem reiniciar a conversa; credenciais ficam apenas em memória
Integração#
import 'package:sulfite_chat/sulfite_chat.dart';
SulfiteDesigner(
initialReport: myReport,
chatConfig: ChatConfig(
name: 'Sulfite Agent',
model: 'gpt-4o',
provider: 'openai',
apiKey: 'sk-...',
systemPrompt: '', // vazio = usa o system prompt padrão com o schema completo
iconName: 'auto_awesome', // smart_toy | auto_awesome | psychology | code | bar_chart
colorValue: 0xFF6750A4,
),
)
Sem chatConfig, o botão de chat não aparece na toolbar — sem impacto no bundle.
SulfiteChatScreen standalone#
SulfiteChatScreen(
config: myConfig,
currentReport: currentReport, // contexto opcional
onReportGenerated: (report) => viewModel.loadReport(report),
onJsonGenerated: (json) => print(json), // JSON bruto
onConfigChange: (newConfig) => setState(() => config = newConfig),
)
Visualizador de relatórios (SulfiteReportViewer)#
O SulfiteReportViewer é o widget de visualização paginada de relatórios renderizados. Ele é usado automaticamente no modo
Preview do designer e pode ser usado de forma standalone para exibir relatórios ao usuário final.
import 'package:sulfite_studio/sulfite_studio.dart';
SulfiteReportViewer(
report: reportDefinition,
data: processedData,
onExportPdf: () => _exportPdf(),
onExportHtml: () => _exportHtml(),
onExportCsv: () => _exportCsv(),
onExportExcel: () => _exportExcel(),
)
Toolbar do viewer#
A toolbar possui controles de navegação, zoom e ações:
| Grupo | Controles |
|---|---|
| Navegação | Primeira / anterior / próxima / última página + campo de salto direto |
| Zoom | Diminuir / aumentar / resetar (também via scroll do mouse e pinch) |
| Texto | Copiar todo o texto do relatório para área de transferência |
| Captura | Captura da página atual (PNG) ou impressão de todas as páginas (PDF vetorial) |
| Exportação | Botões individuais para PDF, HTML, CSV, Excel — visíveis apenas se os callbacks forem fornecidos |
Atalhos de teclado
| Tecla | Ação |
|---|---|
← / PageUp | Página anterior |
→ / PageDown | Próxima página |
Home | Primeira página |
End | Última página |
+ / numpad + | Zoom in |
- / numpad - | Zoom out |
0 / numpad 0 | Resetar zoom |
Captura e impressão#
O botão câmera oferece dois modos:
- Captura da página atual — captura a página visível como PNG em alta resolução (2×) e exibe um overlay com animação estilo macOS (contração para o canto com blur). O overlay oferece ações: Salvar (abre diálogo nativo de destino), Compartilhar (gera PDF e chama share sheet) e Imprimir (diálogo de impressão do sistema).
-
Imprimir todas as páginas — usa o
PdfRendererpara gerar um PDF vetorial com todas as páginas e abre diretamente o diálogo de impressão do sistema. Disponível apenas quando o relatório tem mais de uma página.
macOS: todas as chamadas de impressão usam
dynamicLayout: falsepara evitar o deadlock conhecido do packageprinting(dart_pdf#1878).
Texto selecionável#
Cada elemento de texto (TextElement, FieldElement, RichTextElement) é individualmente selecionável. O usuário pode selecionar e copiar partes específicas do conteúdo. A seleção é por elemento — não existe uma SelectionArea global que interfira com gestos de scroll/zoom.
Propriedades#
| Prop | Tipo | Descrição |
|---|---|---|
report | ReportDefinition | Layout, bands e elementos |
data |
ProcessedData |
Dados processados pelo DataProcessor |
onExportPdf |
VoidCallback? |
Callback para exportar PDF; se null, botão oculto |
onExportHtml |
VoidCallback? |
Callback para exportar HTML; se null, botão oculto |
onExportCsv |
VoidCallback? |
Callback para exportar CSV; se null, botão oculto |
onExportExcel |
VoidCallback? |
Callback para exportar Excel; se null, botão oculto |
backgroundColor |
Color |
Cor do fundo ao redor das páginas (default:
#E8EAED
)
|
pageShadowColor |
Color? |
Cor da sombra das páginas (default: preto com 18% opacidade) |
Tela de consumo (SulfiteConsumerScreen)#
Widget pronto para uso em apps que exibem relatórios ao usuário final — combina coleta de parâmetros com o viewer completo.
SulfiteConsumerScreen(
report: reportDefinition,
data: processedData,
title: 'Relatório de Vendas',
onParamsChanged: (params) async {
final newData = await DataProcessor().process(report, rawData, params: params);
setState(() => data = newData);
},
onExportPdf: _exportPdf,
onExportHtml: _exportHtml,
onExportCsv: _exportCsv,
onExportExcel: _exportExcel,
lookupResolver: HttpLookupResolver(),
)
Se o relatório tiver parâmetros, um botão de filtro aparece na AppBar para abrir o SulfiteFilterScreen.
API pública#
// Widget principal
SulfiteDesigner // designer completo; aceita reportManager: Widget?
// Tela de splash / carregamento
StudioSplashScreen
// Tema
SulfiteTheme
// Toolbar
SulfiteToolbar
// Campos de propriedade
PropertyField
ColorPickerField
FormatPickerField
RoundingConfigEditor
// Componentes comuns
StudioColorPickerDialog // seletor de cor reutilizável
StudioFloatingCard // card com header/body/footer e sombra dupla
ThemeEditor // editor visual de tema+idioma
// Visualizador
SulfiteReportViewer // viewer paginado com toolbar completa
SulfiteConsumerScreen // tela pronta para usuário final (viewer + filtros)
// Diálogos
ValidationDialog
BandManagerDialog
SulfiteFilterScreen // diálogo de filtros com lookups e cascata
// Editores
TransformPipelineEditor
ExpressionEditorBackend
// Preferências
StudioPreferences
PreferencesService
PreferencesProvider
// Validação
ReportValidator
// Configurações
SettingsScreen
Gerenciamento de conexões (
ConnectionManagerWidget,MutableConnectionRegistry) está no pacotesulfite_report_manager— veja sulfite_report_manager → Gerenciar conexões.