Skip to content

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+
WebPlayground onlineNavegador 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:

yaml
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:

dart
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
initialReportReportDefinitionDefinição inicial carregada no canvas
initialSampleDataMap<String, dynamic>?Dados de amostra para o modo Preview
dataSourceResolversList<DataSourceResolver>Resolvers para datasources externos (HTTP, Postgres etc.)
onSaveFuture<void> Function(ReportDefinition)?Chamado ao salvar; ausente = botão salvar oculto
lookupResolverLookupResolver?Resolver de lookups para o SulfiteFilterScreen
reportManagerWidget?Widget de biblioteca de relatórios (ver abaixo)
scriptHandlersMap<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.

dart
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:

dart
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 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 dadosdetailDataSource vinculado
Quebra de páginaTodosSe true, insere quebra antes da band
Visibilidadeheader, footerDropdown com BandVisibility — controla em quais páginas a band é renderizada
Mostrar na páginaheader, footerNúmero de página específico (1-based); sobrepõe visibility
Imprimir quandoTodosExpressã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

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
dart
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 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
primaryColorblueNome de cor pré-definida ou string hex de 6 chars (ex: 4A90E2)
languagept-BRIdioma 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
openaigpt-4.1, gpt-4o, gpt-4o-mini, gpt-4-turbo, gpt-3.5-turbo, o4-miniapiKey
anthropicclaude-sonnet-4-20250514, claude-opus-4-20250514, claude-3-5-haiku-20241022apiKey
ollamallama3.1, llama3, mistral, codellama, qwen2.5-coderbaseUrl (local)
gh_modelsopenai/gpt-4.1, meta/llama-4-scout, mistral/mistral-largetoken GitHub
gh_copilotcopilot (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

dart
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

dart
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.

dart
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çãoPrimeira / anterior / próxima / última página + campo de salto direto
ZoomDiminuir / aumentar / resetar (também via scroll do mouse e pinch)
TextoCopiar todo o texto do relatório para área de transferência
CapturaCaptura da página atual (PNG) ou impressão de todas as páginas (PDF vetorial)
ExportaçãoBotõ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
dataProcessedDataDados processados pelo DataProcessor
onExportPdfVoidCallback?Callback para exportar PDF; se null, botão oculto
onExportHtmlVoidCallback?Callback para exportar HTML; se null, botão oculto
onExportCsvVoidCallback?Callback para exportar CSV; se null, botão oculto
onExportExcelVoidCallback?Callback para exportar Excel; se null, botão oculto
backgroundColorColorCor do fundo ao redor das páginas (default: #E8EAED)
pageShadowColorColor?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.

dart
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

dart
// 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.

Sulfite do 🇧🇷 para o mundo © 2026 Rafael S. Pinheiro