Drag and Drop

This commit is contained in:
2026-03-04 12:35:36 -03:00
parent 007f6c818e
commit 1f4ebb4686
5 changed files with 92 additions and 4 deletions
+1
View File
@@ -9,6 +9,7 @@ use ui::app::App;
fn main() -> iced::Result { fn main() -> iced::Result {
iced::application("Comparador de Notas", App::update, App::view) iced::application("Comparador de Notas", App::update, App::view)
.subscription(App::subscription)
.theme(|_app| ui::theme::tema_dark()) .theme(|_app| ui::theme::tema_dark())
.window(iced::window::Settings { .window(iced::window::Settings {
size: iced::Size::new(1024.0, 768.0), size: iced::Size::new(1024.0, 768.0),
+38
View File
@@ -284,3 +284,41 @@ exceto `GerenciandoLayouts` e `Analisando`.
- `ResultadoPendente` é o tipo intermediário entre a thread de análise e a UI. - `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`. **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.
+36
View File
@@ -92,6 +92,9 @@ pub struct App {
// Resultado anterior (preservado durante reanálise) // Resultado anterior (preservado durante reanálise)
pub resultado_anterior: Option<ResultadoAnalise>, pub resultado_anterior: Option<ResultadoAnalise>,
// Drag-and-drop: indica que um arquivo está sendo arrastado sobre a janela
pub arquivo_em_hover: bool,
} }
impl Default for App { impl Default for App {
@@ -116,6 +119,7 @@ impl Default for App {
itens_por_pagina: 100, itens_por_pagina: 100,
preview_arquivo: None, preview_arquivo: None,
resultado_anterior: None, resultado_anterior: None,
arquivo_em_hover: false,
} }
} }
} }
@@ -659,6 +663,21 @@ impl App {
Task::none() 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(), Message::Noop => Task::none(),
} }
} }
@@ -720,6 +739,23 @@ impl App {
} }
} }
// ─── Subscription (eventos de janela) ───────────────────────────────────
pub fn subscription(&self) -> iced::Subscription<Message> {
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 ────────────────────────────────────────────────────────── // ─── Breadcrumb ──────────────────────────────────────────────────────────
fn view_breadcrumb(&self) -> Element<'_, Message> { fn view_breadcrumb(&self) -> Element<'_, Message> {
+6
View File
@@ -25,6 +25,12 @@ pub enum Message {
// --- Arquivo --- // --- Arquivo ---
SelecionarArquivo, SelecionarArquivo,
ArquivoSelecionado(PathBuf), 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), AbaSelecionada(String),
// --- XLSX: abas carregadas em background --- // --- XLSX: abas carregadas em background ---
+11 -4
View File
@@ -13,6 +13,8 @@ pub fn view(app: &App) -> Element<'_, Message> {
(app.nome_arquivo.clone(), true) (app.nome_arquivo.clone(), true)
}; };
let em_hover = app.arquivo_em_hover;
let icone_arquivo: Element<Message> = container( let icone_arquivo: Element<Message> = container(
text(if tem_arquivo { "CSV / XLSX" } else { "Arquivo" }) text(if tem_arquivo { "CSV / XLSX" } else { "Arquivo" })
.size(12) .size(12)
@@ -64,7 +66,7 @@ pub fn view(app: &App) -> Element<'_, Message> {
.into() .into()
} else { } else {
column![ column![
text("Selecione um arquivo CSV ou XLSX") text("Selecione ou arraste um arquivo CSV ou XLSX")
.size(14) .size(14)
.color(t::TEXT_SECONDARY), .color(t::TEXT_SECONDARY),
text("Suportado: .csv, .xlsx, .xls") text("Suportado: .csv, .xlsx, .xls")
@@ -85,13 +87,18 @@ pub fn view(app: &App) -> Element<'_, Message> {
.style(move |_theme| iced::widget::container::Style { .style(move |_theme| iced::widget::container::Style {
background: Some( background: Some(
iced::Color { iced::Color {
a: 0.05, a: if em_hover { 0.12 } else { 0.05 },
..t::PRIMARY ..t::PRIMARY
} }
.into(), .into(),
), ),
border: iced::Border { border: iced::Border {
color: if tem_arquivo { color: if em_hover {
iced::Color {
a: 0.9,
..t::PRIMARY
}
} else if tem_arquivo {
iced::Color { iced::Color {
a: 0.5, a: 0.5,
..t::PRIMARY ..t::PRIMARY
@@ -102,7 +109,7 @@ pub fn view(app: &App) -> Element<'_, Message> {
..t::BORDER ..t::BORDER
} }
}, },
width: 1.5, width: if em_hover { 2.0 } else { 1.5 },
radius: 8.0.into(), radius: 8.0.into(),
}, },
..Default::default() ..Default::default()