diff --git a/src/main.rs b/src/main.rs index 3818854..16179c7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ use ui::app::App; fn main() -> iced::Result { iced::application("Comparador de Notas", App::update, App::view) + .subscription(App::subscription) .theme(|_app| ui::theme::tema_dark()) .window(iced::window::Settings { size: iced::Size::new(1024.0, 768.0), diff --git a/src/ui/AGENTS.md b/src/ui/AGENTS.md index 6c2e1a8..0936078 100644 --- a/src/ui/AGENTS.md +++ b/src/ui/AGENTS.md @@ -284,3 +284,41 @@ exceto `GerenciandoLayouts` e `Analisando`. - `ResultadoPendente` é o tipo intermediário entre a thread de análise e a UI. **Não bloquear a thread principal da UI.** Qualquer operação lenta deve usar `Task`. + +--- + +## 11. Drag-and-drop de arquivos + +O iced 0.13 expõe eventos de janela para drag-and-drop via `iced::window::Event`. +O projeto os captura através de `App::subscription()` registrado em `main.rs`. + +### Eventos capturados + +| Evento iced | Mensagem emitida | Efeito | +|---|---|---| +| `window::Event::FileDropped(path)` | `Message::ArquivoSolto(path)` | Processa o arquivo como se fosse selecionado via botão | +| `window::Event::FileHovered(_)` | `Message::ArquivoEmHover` | Liga `App.arquivo_em_hover = true` | +| `window::Event::FilesHoveredLeft` | `Message::ArquivoHoverSaiu` | Liga `App.arquivo_em_hover = false` | + +### Subscription + +`App::subscription()` em `app.rs` usa `iced::event::listen_with` para filtrar apenas +os três eventos acima. Está registrado em `main.rs` via `.subscription(App::subscription)`. + +### Estado de hover + +O campo `App.arquivo_em_hover: bool` é `true` enquanto um arquivo está sendo arrastado +sobre a janela. A tela `screens/import.rs` usa esse campo para alterar visualmente a +`drop_zone`: borda mais brilhante (`a: 0.9`) e mais espessa (`width: 2.0`) durante o hover. + +### Fluxo de processamento + +`Message::ArquivoSolto` chama `self.processar_arquivo_selecionado(caminho)` — o mesmo +método chamado pelo botão de seleção de arquivo. O comportamento é idêntico: validação +de extensão, leitura de abas (XLSX) ou preview (CSV), e transição de estado. + +### Como estender + +Para adicionar suporte a drag-and-drop em outras telas (ex: importar layout JSON por +drag), basta verificar `App.estado` dentro do arm `Message::ArquivoSolto` antes de +chamar `processar_arquivo_selecionado`, e desviar conforme necessário. diff --git a/src/ui/app.rs b/src/ui/app.rs index 05ced95..7199cd4 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -92,6 +92,9 @@ pub struct App { // Resultado anterior (preservado durante reanálise) pub resultado_anterior: Option, + + // Drag-and-drop: indica que um arquivo está sendo arrastado sobre a janela + pub arquivo_em_hover: bool, } impl Default for App { @@ -116,6 +119,7 @@ impl Default for App { itens_por_pagina: 100, preview_arquivo: None, resultado_anterior: None, + arquivo_em_hover: false, } } } @@ -659,6 +663,21 @@ impl App { Task::none() } + // ── Drag-and-drop ── + Message::ArquivoSolto(caminho) => { + self.arquivo_em_hover = false; + self.processar_arquivo_selecionado(caminho) + } + Message::ArquivoEmHover => { + self.arquivo_em_hover = true; + Task::none() + } + Message::ArquivoHoverSaiu => { + self.arquivo_em_hover = false; + Task::none() + } + + // ── Sem operação ── Message::Noop => Task::none(), } } @@ -720,6 +739,23 @@ impl App { } } + // ─── Subscription (eventos de janela) ─────────────────────────────────── + + pub fn subscription(&self) -> iced::Subscription { + iced::event::listen_with(|event, _status, _id| match event { + iced::Event::Window(iced::window::Event::FileDropped(path)) => { + Some(Message::ArquivoSolto(path)) + } + iced::Event::Window(iced::window::Event::FileHovered(_)) => { + Some(Message::ArquivoEmHover) + } + iced::Event::Window(iced::window::Event::FilesHoveredLeft) => { + Some(Message::ArquivoHoverSaiu) + } + _ => None, + }) + } + // ─── Breadcrumb ────────────────────────────────────────────────────────── fn view_breadcrumb(&self) -> Element<'_, Message> { diff --git a/src/ui/message.rs b/src/ui/message.rs index b1241c4..964db43 100644 --- a/src/ui/message.rs +++ b/src/ui/message.rs @@ -25,6 +25,12 @@ pub enum Message { // --- Arquivo --- SelecionarArquivo, ArquivoSelecionado(PathBuf), + /// Arquivo arrastado e solto sobre a janela (drag-and-drop). + ArquivoSolto(PathBuf), + /// Arquivo está sendo arrastado sobre a janela (hover). + ArquivoEmHover, + /// Arquivo arrastado saiu da janela sem ser solto. + ArquivoHoverSaiu, AbaSelecionada(String), // --- XLSX: abas carregadas em background --- diff --git a/src/ui/screens/import.rs b/src/ui/screens/import.rs index 04d2455..59e4587 100644 --- a/src/ui/screens/import.rs +++ b/src/ui/screens/import.rs @@ -13,6 +13,8 @@ pub fn view(app: &App) -> Element<'_, Message> { (app.nome_arquivo.clone(), true) }; + let em_hover = app.arquivo_em_hover; + let icone_arquivo: Element = container( text(if tem_arquivo { "CSV / XLSX" } else { "Arquivo" }) .size(12) @@ -64,7 +66,7 @@ pub fn view(app: &App) -> Element<'_, Message> { .into() } else { column![ - text("Selecione um arquivo CSV ou XLSX") + text("Selecione ou arraste um arquivo CSV ou XLSX") .size(14) .color(t::TEXT_SECONDARY), text("Suportado: .csv, .xlsx, .xls") @@ -85,13 +87,18 @@ pub fn view(app: &App) -> Element<'_, Message> { .style(move |_theme| iced::widget::container::Style { background: Some( iced::Color { - a: 0.05, + a: if em_hover { 0.12 } else { 0.05 }, ..t::PRIMARY } .into(), ), border: iced::Border { - color: if tem_arquivo { + color: if em_hover { + iced::Color { + a: 0.9, + ..t::PRIMARY + } + } else if tem_arquivo { iced::Color { a: 0.5, ..t::PRIMARY @@ -102,7 +109,7 @@ pub fn view(app: &App) -> Element<'_, Message> { ..t::BORDER } }, - width: 1.5, + width: if em_hover { 2.0 } else { 1.5 }, radius: 8.0.into(), }, ..Default::default()