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_studioO 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: [
PostgresDataSourceResolver(registry: registry),
RestDataSourceResolver(),
],
reportManager: ReportManagerWidget(
repository: SqliteReportRepository(db),
canInsert: true,
canEdit: true,
canDelete: true,
canSettings: true,
onEditReport: (ctx, entry, definition) {
// Carregar o relatório selecionado no canvas atual
Navigator.of(ctx).pop();
viewModel.loadReport(definition);
},
),
)Para expor o gerenciador de conexões junto com a biblioteca:
reportManager: Column(
children: [
Expanded(
child: ReportManagerWidget(
repository: SqliteReportRepository(db),
onEditReport: (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
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
Scripts (afterQuery / beforeRender Hooks)
Deprecated: The transforms field has been removed in favor of the scripting system. Data transformations should now be implemented via scripts with afterQuery and beforeRender hooks.
See Expressions and RFC-016 for details on the new scripting architecture using Dart code with sandbox execution.
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
SettingsScreenGerenciamento de conexões (
ConnectionManagerWidget,MutableConnectionRegistry) está no pacotesulfite_report_manager— veja sulfite_report_manager → Gerenciar conexões.