-
Notifications
You must be signed in to change notification settings - Fork 1
Photon pt BR
🌐 Esta página em: English · Português
O Photon é o motor de renderização GPU proprietário do eQuantic.UI para alvos nativos: um renderizador escrito do zero, inspirado no Impeller, que desenha a árvore de componentes pixel a pixel na GPU (Metal no macOS/iOS, Vulkan no Android), sem camada Skia e sem WebView. Ele é o segundo alvo de realização da arquitetura de componentes write-once: os mesmos componentes C# que rebaixam para DOM+CSS no web são rasterizados nativamente pelo Photon.
Ter uma camada nativa própria significa ter a pilha de renderização inteira: backends Metal/Vulkan diretos com shaders pré-compilados, não um wrapper sobre o Skia. Isso compra:
- Performance previsível: registro fixo de estados de pipeline, zero compilação de shader em tempo de execução (a fonte de travadas que o Impeller foi construído para matar). Os caches de pipeline persistem entre lançamentos nos dois backends.
- Uma superfície minúscula: renderizar interface precisa de um punhado de primitivas, não de uma biblioteca 2D geral.
- Um único modelo normativo: a mesma matemática, testável até o pixel, entre a referência em CPU e todos os backends de GPU.
Componentes C# (write-once, vocabulário do eQuantic.UI.Primitives)
│ Build(ComponentContext) // puro, baseado em tokens, sem modos
▼
eQuantic.UI.Native.Framework // motor de layout flex em C# (spec A2)
▼
eQuantic.UI.Native.Components // PhotonRealizer: árvore abstrata → display list
▼
eQuantic.UI.Native.Engine // geometria, modelo de cor, Sdf.cs (NORMATIVO), DisplayList
▼
IRenderBackend
├── eQuantic.UI.Native.Engine.Reference // rasterizador escalar em CPU (verdade das goldens, nunca publicado)
├── eQuantic.UI.Native.Engine.Metal // GPUs da Apple (macOS, iOS)
└── eQuantic.UI.Native.Engine.Vulkan // Android
eQuantic.UI.Native.Shell.MacOS / .iOS / .Android // a janela/host de cada plataforma
-
Sdf.csé uma especificação, não uma biblioteca: distância com sinal de retângulo arredondado por canto, traço centrado (|d| − w/2), rampa de cobertura de antialiasing de 1 pixel. Todo rasterizador, tanto a referência em CPU quanto os fragment shaders da GPU, implementa exatamente essas fórmulas. - Modelo de cor: autoria em sRGB → mistura linear pré-multiplicada → armazenamento em sRGB, espelhando o comportamento do hardware GPU com render targets sRGB.
-
Display lists: registros
DrawCommandplanos e sem heap (Clear / FillRRect / StrokeRRect, tintas sólidas ou de gradiente linear de duas paradas, transforms 2D já assadas).
Toda cena renderiza por um backend numa superfície, lê os pixels sRGB de volta e compara contra PNGs golden comitados (codec sem dependências, fluxo de regeneração com EQ_UPDATE_GOLDENS=1, tolerância ±2, artefatos de falha com diffs amplificados). O catálogo de cenas é compartilhado: o backend Reference pina as goldens, os backends de GPU rodam o mesmo catálogo para paridade, e a paridade GPU↔CPU se sustenta em todo o catálogo compartilhado dentro da tolerância das goldens, no Metal e no Vulkan.
Os backends de GPU são dirigidos por bindings finos e tipados de objc_msgSend/P-Invoke: sem framework de binding, sem shims em C, zero pacotes de terceiros.
O realizador nativo desenha chrome de componente de verdade (preenchimentos resolvidos por token, bordas internas, raios por canto):
| Subsistema | Estado |
|---|---|
| Formas, bordas, gradientes, transforms, mistura, clip | ✅ Completo, testado por golden |
| Layout (flex + wrap, grid, stacks, adaptativo, truncamento) | ✅ Motor em C# |
| Sombras (elevação §05) | ✅ ShadowCoverage analítico, nos dois backends |
| Texto | ✅ REAL. Primitiva Texture (cobertura A8 × tinta) + ITextRasterizer da plataforma; no macOS = CoreText (só frameworks do sistema, sem HarfBuzz/FreeType onde a plataforma tem um motor de texto). Um motor mede E rasteriza, então as quebras de linha concordam por construção. Duas faces: a proporcional do sistema e a monoespaçada dele (TypeStyle.Mono). Nenhum glifo é jamais cortado: o bitmap é dimensionado pela caixa de TINTA da linha (CTLineGetImageBounds, não as métricas declaradas da fonte, já que a cauda profunda de um g passa além delas) e reporta o quanto acima da caixa de linha ele precisou alcançar, então o realizador levanta o desenho exatamente por isso e a caixa de linha continua caindo onde o layout a colocou |
| Ícones | ✅ REAL. Parser de caminho SVG em C# puro (conjunto completo de comandos, arcos via F.6.5) + rasterizador CoreGraphics, a mesma primitiva Texture |
| Opacidade/transforms de grupo | ✅ Camadas do motor |
| Compositor de rolagem | ✅ Deslocamentos por caminho, fixação Sticky de verdade (vertical) |
| Movimento | ✅ Movimento em laço, transições de valor, entrada/saída de Presence com replay de snapshot de comandos |
| Gestos | ✅ Pipeline de hover, toque com cancelamento por folga, arrastar-para-dispensar (seguir + deslizar) |
| Overlays ancorados | ✅ Camadas de overlay sintéticas posicionadas a partir dos limites absolutos da âncora |
| Imagens | Caixa SurfaceSubtle, já que decodificação/upload não faz parte da versão atual (o consumidor de Texture que falta) |
Desde 0.2.0-preview.28
Um parágrafo rico (Text.Spans) rasterizava como UM bloco no estilo base, então a palavra em
negrito, o span de código inline, o termo em itálico e o link no meio da frase se dissolviam todos
em prosa chapada, a divergência write-once mais larga que restava, já que o web emite um span por
run desde o dia em que runs existem. A unidade agora é um FRAGMENTO, um run numa linha, porque essa
é a única peça com um retângulo e as duas metades precisam de um: cada fragmento rasteriza no
próprio estilo (peso, mono, itálico, o tamanho próprio), e o retângulo de um fragmento com link é
uma região de hit de onde o shell navega, então links inline são tocáveis no Photon, fechando o item do
Roadmap que os nomeava.
A interação está ligada: o PhotonHost guarda uma raiz retida, o SetState invalida, os toques despacham para as regiões de hit (o de cima vence, o desabilitado engole; ≥48dp sob um dedo, o tamanho próprio do controle sob um ponteiro, veja Densidade). O Counter nativo roda toque → SetState → reconstrução de ponta a ponta nos testes. O PhotonHost.FocusedPath reporta o que segura o teclado, incluindo um campo em edição (o cursor é o indicador de foco daquele campo), porque uma ponte de acessibilidade da plataforma precisa de uma resposta honesta, não de metade dela.
Desde 0.2.0-preview.24
Os eventos de ponteiro carregam um PointerKind, por EVENTO e não por host, porque a capacidade de
hover pertence ao dispositivo na mão (um iPad ganha um trackpad, um Android ganha um mouse). Um
PointerMove marcado como Touch guia arrastos, panorâmicas e seleções exatamente como um mouse e
pula apenas a resolução de hover; um PressUp/PointerCancel de toque limpa qualquer hover que uma
sessão de entrada mista tenha deixado para trás, enquanto um mouse mantém o hover dele durante um
clique. O shell do macOS instala uma NSTrackingArea e roteia MouseExited para PointerLeave(),
então cruzar a borda da janela limpa o hover em vez de travá-lo.
A identidade do hover é o CAMINHO de layout, não a referência do nó, e o toque aprendeu isso primeiro:
uma reconstrução de componente substitui cada instância, e um hover que só conhecia a referência
antiga pintava exatamente um frame. Ele também é uma CADEIA de toda região sob o ponteiro (o :hover
do CSS casa com todo ancestral, e o realizador nativo responde a mesma pergunta que o gêmeo web
responde), que é o que permite a um Anchored aberto por hover ficar aberto enquanto você faz hover no
gatilho dentro dele. O pressionado continua vencendo o hover na caixa que consumiu a troca.
Desde 0.2.0-preview.24
Um processo gráfico lançado fora de um terminal não tem LANG/LC_*, então o .NET começa invariante
mesmo numa máquina pt-BR. Os shells agora resolvem a verdade de locale da plataforma e a aplicam antes
do primeiro frame: o AppleLocale lê o NSLocale, onde preferredLanguages[0] é a cultura de interface
(recursos) e currentLocale a cultura de formato (datas, separadores), nativamente o mesmo par que o
.NET modela como CurrentUICulture/CurrentCulture, e o AndroidLocale alimenta os dois statics a
partir de Locale.getDefault(). O PhotonCultureController é dono da escrita dos statics (thread
padrão E thread atual, para que toda thread posterior concorde) e repinta no Apply, a forma do
controller de tema; registrado com TryAdd, então um app que força uma cultura vence. Observar o locale
do SO mudando em tempo de execução é uma cerca por shell.
O motor nativo é julgado contra o browser, porque o gêmeo web renderiza a mesma árvore lá:
-
O Fill herda a indeterminação: num eixo em que o pai se dimensiona pelo conteúdo, um filho
Fillnão tem o que preencher e passa a pergunta adiante; o que sobra não é distribuído nesse eixo. -
Semântica de bloco na largura, e uma altura DECIDIDA se entrega ao filho: uma caixa cuja largura foi decidida estica um filho CONTÊINER de tamanho automático por ela (o filho div de uma div ocupa a largura toda), enquanto um botão, link ou input se aperta ali: eles são inline-block, e só um esticamento de FLEX atravessa por eles (
StretchKind). A metade da altura está logo abaixo.
Desde 0.2.0-preview.28
Uma caixa cuja altura foi DECIDIDA (fixa, preenchida ou limitada) a entrega ao filho único, nos
dois alvos. Esta é a regra que aposentou a família recorrente da "toolbar colada no topo da barra
de altura fixa", em que toda Row dentro de um Box de 64dp precisava de um Height = Fill manual
para alcançar o fundo da própria barra.
- flex-shrink com piso de min-content: uma linha que não cabe encolhe os itens em vez de transbordar, e nunca abaixo do min-content de cada item (a palavra mais longa de um texto, a soma dos filhos de uma linha). O piso importa em dobro porque o clip guarda pixels E ponteiro: uma região de toque cortada não pode ser tocada.
- Refluxo de Min/Max: quando um limite muda a extensão de uma caixa, o filho dela se remede contra o tamanho final.
macOS (eQuantic.UI.Native.Shell.MacOS): uma NSWindow hospedando uma CAMetalLayer, zero pacotes de terceiros, apenas bindings finos e tipados de objc_msgSend para o AppKit. O backend Metal codifica cada frame direto no drawable da camada (cache de pipeline por formato de pixel, BGRA8_sRGB para janelas); o PhotonHost.RenderScale rasteriza em backingScaleFactor enquanto layout/entrada ficam em dp (retina). Eventos de mouse/rolagem/teclado do SO entram no pipeline de entrada comum, então os visuais de toque, os diffs de hover e os menus ancorados se comportam exatamente como nos testes. Texto San Francisco de verdade e ícones de pacote de verdade renderizam na tela. Experimente: dotnet run --project samples/PhotonDesktop. Dois modos sem tela não precisam de janela nenhuma: --Photon:MaxFrames 120 apresenta essa quantidade de frames e sai, e --Photon:ScreenshotPath out.png (com --Photon:Mode Dark para a outra paleta) renderiza UM frame assentado pelo backend de referência com as mesmas métricas do CoreText. Esse é o passo de screenshot do CI, e o jeito de conferir uma passada de fidelidade contra um handoff de design. Chamar Run() fora da thread principal diz isso em termos de .NET, em vez de morrer dentro do AppKit. Redimensione a janela à vontade: o host adota o viewport com todo o estado intacto.
iOS (eQuantic.UI.Native.Shell.iOS): o PhotonApp.Run é o ponto de entrada; o mesmo motor apresenta pelo Metal.
Android (eQuantic.UI.Native.Shell.Android): o app fornece o CreateApp (sem Main); a apresentação vai pelo Vulkan (o backend Reference é o fallback).
Uma seleção tem duas pontas e só uma delas se move. Qual é não é algo que uma pessoa consiga deduzir da faixa, então o cursor é desenhado JUNTO com a seleção, não no lugar dela: a faixa diz quais caracteres estão segurados, o cursor diz qual PONTA você está segurando, a que a seta com ⇧ vai mover, a partir da qual o próximo caractere substitui.
Duas regras vêm junto, e as duas são sobre estar visível quando importa:
- A fase do piscar começa no último MOVIMENTO do cursor, não na época. Numa fase de rodagem livre, um cursor que acabou de chegar em algum lugar pode estar na metade APAGADA por meio segundo: você aperta ⇧→ e olha para nada. Toda escrita no cursor reinicia o ciclo, que é por que o host roteia todas elas por uma única propriedade.
- Enquanto um arrasto está desenhando uma seleção o cursor não pisca de jeito nenhum. A ponta que segue o seu ponteiro é a única coisa que você está olhando.
No web o campo é um <input> de verdade e o browser é dono do cursor dele; o que está descrito aqui é
o que o Photon pinta, e o que a superfície de editor (que os dois alvos pintam eles mesmos) faz.
Um cursor é movimento de 2Hz, não movimento de vsync, então um cursor em edição AGENDA os frames dele em vez de manter o laço de render quente:
- o
RenderFramedeixaNeedsRendercomo falso quando o cursor é a única razão para um próximo frame, e registra quando a próxima TRANSIÇÃO do piscar é devida; - os ticks ociosos dos shells (o timer de 120Hz do macOS, os callbacks de vsync dos telefones)
perguntam
host.IsFrameDue(nowMs)junto comNeedsRender: uma apresentação por meio período, caindo na transição, ~2 por segundo em vez da taxa de atualização do display.
Movimento de verdade (um spinner, um deslize, uma transição) continua mantendo o laço quente, e o
agendamento existe só para o cursor. Enquanto um arrasto desenha uma seleção nada é agendado: o cursor
é forçado sólido e o movimento do próprio ponteiro guia os frames. Dez segundos simulados de um campo
focado: 20 apresentações, cada uma virando o piscar, afirmado em CaretPacingTests, contagem incluída.
O web não precisa de nada disso: o browser é dono do cursor do <input>, e a div de cursor da
superfície de editor pode piscar no compositor.
Rode qualquer app Photon sob dotnet watch run, edite um corpo de Build, salve, e a janela redesenha
com o novo código e TODO o estado intacto, num tique. Sem comando de SDK, sem configuração: o
encanamento vem em eQuantic.UI.Native.Components e todo host se inscreve na construção.
A arquitetura tem formato de hot reload por construção:
- a árvore é RE-EXPANDIDA a partir da raiz retida a cada frame (o
Buildroda durante a medição), então um corpo de método remendado simplesmente passa a valer no frame seguinte; - o estado do componente vive num armazenamento com chave de CAMINHO, não nos objetos que uma edição substitui, então nada precisa ser migrado. Três cliques de estado de contador sobrevivem a um reload porque nada os tocou.
O PhotonHotReload (o [MetadataUpdateHandler]) fornece a peça que falta, o próprio frame seguinte,
porque uma janela ociosa não apresenta nada e uma atualização de metadados chega sem nenhum evento de
entrada anexado. Ele faz exatamente duas coisas:
- acorda todo host vivo por um bool que os laços de render já consultam (macOS em até 50ms, os telefones no próximo vsync deles), e
- incrementa uma GERAÇÃO que o host compara na própria thread de render dele para descartar os caches de conteúdo uma vez (rasterizações de texto, ícones, imagens). As chaves são conteúdo, então a maioria das edições passaria longe delas, mas um rasterizador remendado ou um caminho de ícone editado é exatamente a edição que uma pessoa faz enquanto ajusta o visual. Nada é mutado pela thread do handler.
Edições grosseiras (tipos novos, assinaturas mudadas) cabem ao runtime recusar. O dotnet watch
responde a essas com um reinício, o que é correto e não precisa de nada do framework.
Mobile: o mesmo registro serve os hosts de iOS e Android (os ticks de vsync deles consultam o mesmo
bool), então o hot reload funciona no SIMULADOR. Um dispositivo físico precisa que o dotnet watch
alcance o processo, o que é uma questão de canal de deploy, não de framework.
O Sdf.slang (transliterando o Sdf.cs) compila offline pela toolchain slangc pinada em MSL + SPIR-V
comitados: o fragmento SDF mais o fragmento texturizado (Load de texel, o mais próximo por
definição, porque as rasterizações são em escala de dispositivo, com paridade exata com a Reference). Os
desenvolvedores de app nunca rodam a toolchain de shaders; eles consomem os artefatos comitados.
O PerfHarnessTests mede o que a definição de pronto promete ("120 Hz, zero alocações em regime,
orçamentos cumpridos") em vez de assumir. Numa cena em formato de dashboard (24 cartões + uma faixa com
movimento em laço), por frame em regime (baseline série M): 146 comandos de desenho, realize p50
0,23 ms / p95 0,32 ms contra o orçamento de 8,33 ms, e alocação em regime abaixo de 72 KB por frame
com reciclagem de frames ligada (67,5 KB medidos).
O que mantém o número baixo:
- um cache de strings de caminho: um caminho é identidade, então o host empresta um dicionário que sobrevive aos frames e o regime para de reconcatená-las;
- um
DisplayListBuilderreutilizado: oReset()mantém a capacidade do buffer; os shells rodam um builder por laço; -
reciclagem de frames (
PhotonHost.RecycleFrames, opcional, desligado por padrão): oRealizeResultcarrega uma história explícita de propriedade. Com a reciclagem ligada, o host é DONO de todo frame que ele substitui, a árvore substituída alimenta umLayoutNodePool, e o próximo frame é construído a partir dele. Os shells de produção (macOS/iOS/Android) ligam isso porque nada retém os frames deles; testes e ferramentas que seguramRealizeResults deixam desligado e ficam com árvores imutáveis.
Os tetos são pinados como CATRACAS de regressão com uma margem acima dos números medidos (152 KB no perfil padrão / 72 KB com pool, 200 comandos, alarme de 33 ms, com o tempo deliberadamente frouxo para que um CI compartilhado nunca fique instável). A metade de GPU do frame segue coberta por goldens de paridade, não por cronometragem no CI.
Enquanto um campo segura o cursor, o shell do macOS entrega os eventos de tecla ao contexto de entrada
da PLATAFORMA (NSTextInputClient: a view conforma, registrada em tempo de execução como toda outra
sobreposição):
- Uma tecla morta ou um método CJK compõe ao longo de vários toques como texto marcado: segurado
pelo host (
SetMarkedText), renderizado INLINE no cursor com um sublinhado, enquanto o VALOR do campo fica intocado. O commit (CommitText) entra no campo pela mesma porta que todo toque de tecla usa; o cancelamento deixa o campo exatamente como estava. Compor sobre uma seleção a substitui, a mesma regra que digitar tem. Um campo obscurecido compõe às cegas (ecoar a composição ecoaria o segredo). - As teclas de edição VOLTAM do contexto de entrada como seletores (
doCommandBySelector:) e reentram pela mesma portaOnKey, re-soletradas para nomes do DOM: Backspace, setas (com variantes Shift/Alt), Home/End, Escape. Acordes de Command/Control nunca entram no contexto: ⌘A/⌘C/⌘Z pertencem ao app. - O
firstRectForCharacterRangeancora a janela de candidatos no CURSOR (hostCaretRect()→ inversão de y → view → janela → tela), então o seletor CJK aparece sob a composição. - Prova:
ImeCompositionTestsno host (sequência de tecla morta, cancelamento, composição sobre seleção, acompanhamento do cursor, intervalos, perda de foco descarta a composição), e o autoteste da janela dirige os seletores registrados DE VERDADE (setMarkedText:→insertText:) contra um campo vivo. Digiteoption+e eem qualquer campo para ver oécompor. - Escopo: superfícies de código compõem às cegas. O commit funciona, mas a face do editor não renderiza trecho marcado inline.
Desde 0.2.0-preview.29
O Photon desenha os próprios pixels, então o SO vê uma view opaca, e sem uma árvore de semântica o app é INVISÍVEL para um leitor de tela. A árvore é um artefato de primeira classe do frame:
-
SemanticsTree.Collect(frame)(compartilhado, neutro em relação ao alvo) deriva a semântica em ordem de leitura a partir do layout realizado, tanto da página QUANTO das camadas de overlay, então diálogos são vistos. Papéis: StaticText, Button, Link, TextField, CodeField, Slider (Adjustable), Image (Icon rotulado). O texto interno de um controle é o nome acessível dele (a regra<button>texto</button>do web); umIconrotulado anuncia, um sem rótulo é decoração; oTextEntry.Labelnomeia um campo. A identidade é o CAMINHO de layout, a mesma identidade que toques, foco e rolagem usam, porque a árvore é reconstruída a cada frame e referências envelhecem. -
Conteúdo rolado para fora é incluído, exatamente como o
FocusStope deliberadamente ao contrário das regiões de hit de ponteiro: a navegação linear alcança abaixo da dobra; o corte guarda o ponteiro, não o leitor. -
PhotonHost.Semantics()expõe a árvore;PhotonHost.ActivatePath(path)aciona um controle do jeito que um toque aciona (resolvido a partir das regiões do frame atual, recusando desabilitados); elementos ajustáveis respondem a ações de incremento/decremento. -
Ponte do macOS (
PhotonAccessibility): a view de conteúdo respondeaccessibilityChildrencomEQAXElements (umNSAccessibilityElementque pode ser pressionado): papel, rótulo, valor, habilitação,accessibilityFrameInParentSpacecom y invertido, o caminho comoaccessibilityIdentifier, e oaccessibilityPerformPressroteado porActivatePath. Os elementos são reconstruídos a cada consulta; o lote anterior é liberado. -
Ponte do iOS (
PhotonAccessibility): aPhotonViewé um CONTÊINER de acessibilidade cujos elementos são construídos da árvore em ordem de leitura: papel mapeado emUIAccessibilityTraits, frames convertidos para o espaço da tela, o caminho comoaccessibilityIdentifier, e o toque duplo e o gesto de ajuste exportados comoaccessibilityActivate/accessibilityIncrement/accessibilityDecrement, roteados porActivatePatheAdjustPath. Uma marcação é um botão cujo VALOR é"1"/"0", o contrato do próprio UIKit para um alternador, e por isso o estado é anunciado no idioma do usuário sem o framework publicar nenhuma dessas palavras. A divulgação anda noaccessibilityExpandedStatusdo iOS 17. Os elementos são reconstruídos quando a árvore MUDA, e não a cada consulta (o VoiceOver pede a lista enquanto caminha por ela), e oLayoutChangedsó é postado enquanto o VoiceOver está ligado. -
Ponte do Android (
PhotonAccessibility, umAccessibilityNodeProvider): aPhotonSurfaceViewresponde por uma hierarquia VIRTUAL: um filho por nó de semântica, o papel como o nome de classe da plataforma (android.widget.Button,CheckBox,Switch,SeekBar,EditText,ImageView,TextView), limites em pixels de tela,ACTION_CLICKporActivatePath, rolagem para frente/trás porAdjustPath, e a exploração por toque respondida nodispatchHoverEvent. Lá a divulgação é uma AÇÃO em vez de um sinalizador: oferecer expandir é como um nó diz que está fechado. A view precisa se declararIMPORTANT_FOR_ACCESSIBILITY_YES, já que ela é a única view da janela, e sem isso a janela fica sem raiz acessível nenhuma. -
Duas cercas, uma em cada telefone: nenhuma das plataformas tem um terceiro estado de marcação,
então um checkbox
mixedé anunciado sem estado em vez de alegar que está desmarcado; e o Android não tem classe de link, então um link é anunciado como aquilo que ele faz. -
Prova: os
SemanticsTestspinam ordem, nomeação, visibilidade de diálogo, exposição de desabilitados e a ação de ativar, mais a PORTEIRA DE PARIDADESemanticsMatchFocusStops, que impede o percurso de semântica e o percurso de entrada de se afastarem um do outro. O autoteste de cada shell então pergunta à PLATAFORMA, não à árvore: a janela do macOS imprime seusaccessibilityChildrenvivos, o controlador do iOS envia os seletores Objective-C de verdade, e no Android ouiautomator dumplê o app de fora pelo mesmo caminho de serviço que o TalkBack usa. A mesma tela responde com os mesmos 26 elementos nos três. - A paridade no web é nativa do realizador web (
<button>/aria-label/placeholder de verdade), então nada é devido lá. A árvore é neutra em relação ao alvo e agora todo shell tem a sua ponte.
A suíte nativa pina o comportamento do motor com 87 imagens golden comitadas (pares de componentes
claro+escuro, estados de folha no meio do arrasto, cobertura de textura) ao lado das suítes de unidade e
de host. A janela roda o showroom write-once com texto, ícones, menus e gestos de verdade nos shells de
macOS, iOS e Android. Cerca de escopo atual: decodificação/upload de imagem não está incluída, então o
Image renderiza a superfície de placeholder dele.
🌐 English · Português
🏁 Comece aqui
📱 Write-once
- Componentes write-once
- Superfície declarativa
- Motor Photon
- Design System
- Capacidades
- Armazenamento
- Formulários
- Editor de código
- Markdown
- Mermaid
- Renderização de Email
🏗️ Arquitetura
⚙️ Compilação
- Compilador
- Avaliação em tempo de compilação
- Recursos C# suportados
- Resolução de tipos externos
- Fluxo de build
- Diagnósticos
⚡ Runtime
🔌 Servidor
🎨 Ecossistema
🚀 Desenvolvimento