Logosulfite.app
rafagazani/sulfite 999999

Studio

Editor visual Flutter para relatórios Sulfite

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.

PlataformaFormatoRequisitos
macOS.app (universal)macOS 12+
Windows.exe (installer)Windows 10+
Android.apkAndroid 5.0+
LinuxAppImage / .tar.gzUbuntu 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âmetroTipoDescriçã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 StudioFloatingCard e 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 X ou 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:

CampoTiposDescrição
AlturaTodosAltura 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 groupHeader e groupFooter (com campo groupBy e dataSourceId)
  • Reordenar por drag & drop
  • Editar todos os campos da band: altura, fonte de dados, quebra de página, visibilidade, showOnPage, printWhen e groupBy
  • 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:

CamposLayout
X / YMesma linha
Width / HeightMesma linha
Font Size / PaddingMesma linha
Border Color / Border WidthMesma 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

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');
}
  1. Abra a aba Scripts no Studio
  2. Selecione ou crie um script
  3. Certifique-se de ter dados no Preview (aba Preview deve ter sido carregada ao menos uma vez)
  4. Clique em Executar para ver os logs no Console

ScriptContext API

MétodoDescriçã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.reportReportDefinition 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 select com lookup carregam 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ênciaDefaultDescrição
themeModesystemClaro, 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)
showGridtrueExibir grid no canvas
snapToGridtrueSnap de elementos ao grid
gridSize10.0Tamanho do grid em pontos
autoSavetrueSalvar automaticamente
splitViewWeight0.75Proporçã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 campo primaryColor
  • 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#

ProviderModelosAuth
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#

  1. Usuário escreve (texto + imagens opcionais via file picker)
  2. O JSON do relatório atual é injetado como contexto (currentReport)
  3. ReportGenerator chama o LLM via HTTP POST (com retry automático)
  4. A resposta é extraída do bloco ```json ``` e validada contra o schema do sulfite_core
  5. Se inválido, os erros são reenviados ao LLM para auto-correção (até 3 tentativas)
  6. 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:

GrupoControles
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)
TextoCopiar 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

TeclaAção
/ PageUpPágina anterior
/ PageDownPróxima página
HomePrimeira página
EndÚltima página
+ / numpad +Zoom in
- / numpad -Zoom out
0 / numpad 0Resetar 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 PdfRenderer para 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: false para evitar o deadlock conhecido do package printing (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#

PropTipoDescrição
reportReportDefinitionLayout, 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 pacote sulfite_report_manager — veja sulfite_report_manager → Gerenciar conexões.