initial commit

This commit is contained in:
cottongin
2026-02-05 00:15:54 -05:00
commit 016c9d9e33
16 changed files with 7329 additions and 0 deletions

127
src/ui/drop_zone.rs Normal file
View File

@@ -0,0 +1,127 @@
use eframe::egui::{self, Color32, Stroke, Vec2};
use std::path::PathBuf;
pub struct DropZone {
is_hovering: bool,
}
impl DropZone {
pub fn new() -> Self {
Self { is_hovering: false }
}
pub fn show<F>(&mut self, ui: &mut egui::Ui, mut on_files_dropped: F)
where
F: FnMut(Vec<PathBuf>),
{
let available_width = ui.available_width();
let height = 120.0;
// Check for drag-and-drop hover state
let is_hovering = ui.ctx().input(|i| !i.raw.hovered_files.is_empty());
self.is_hovering = is_hovering;
// Style based on hover state
let (bg_color, stroke_color, stroke_width) = if self.is_hovering {
(
Color32::from_rgba_unmultiplied(100, 149, 237, 30), // Cornflower blue, semi-transparent
Color32::from_rgb(100, 149, 237),
2.0,
)
} else {
(
Color32::from_gray(40),
Color32::from_gray(80),
1.0,
)
};
let (rect, response) = ui.allocate_exact_size(Vec2::new(available_width, height), egui::Sense::click());
// Draw background
ui.painter().rect(
rect,
8.0,
bg_color,
Stroke::new(stroke_width, stroke_color),
);
// Draw dashed border effect (using multiple small rects)
if !self.is_hovering {
let dash_length = 10.0;
let gap = 5.0;
let inset = 4.0;
let inner_rect = rect.shrink(inset);
// Top and bottom
let mut x = inner_rect.min.x;
while x < inner_rect.max.x - dash_length {
ui.painter().line_segment(
[egui::pos2(x, inner_rect.min.y), egui::pos2(x + dash_length, inner_rect.min.y)],
Stroke::new(1.0, Color32::from_gray(60)),
);
ui.painter().line_segment(
[egui::pos2(x, inner_rect.max.y), egui::pos2(x + dash_length, inner_rect.max.y)],
Stroke::new(1.0, Color32::from_gray(60)),
);
x += dash_length + gap;
}
// Left and right
let mut y = inner_rect.min.y;
while y < inner_rect.max.y - dash_length {
ui.painter().line_segment(
[egui::pos2(inner_rect.min.x, y), egui::pos2(inner_rect.min.x, y + dash_length)],
Stroke::new(1.0, Color32::from_gray(60)),
);
ui.painter().line_segment(
[egui::pos2(inner_rect.max.x, y), egui::pos2(inner_rect.max.x, y + dash_length)],
Stroke::new(1.0, Color32::from_gray(60)),
);
y += dash_length + gap;
}
}
// Draw text
let text = if self.is_hovering {
"Drop files here"
} else {
"Drag and drop files here\nor click to browse"
};
let text_color = if self.is_hovering {
Color32::WHITE
} else {
Color32::from_gray(180)
};
ui.painter().text(
rect.center(),
egui::Align2::CENTER_CENTER,
text,
egui::FontId::proportional(16.0),
text_color,
);
// Supported formats hint
ui.painter().text(
egui::pos2(rect.center().x, rect.max.y - 20.0),
egui::Align2::CENTER_CENTER,
"GIF, PNG, JPEG, WebP, MP4, MOV, WebM",
egui::FontId::proportional(11.0),
Color32::from_gray(120),
);
// Handle click to open file dialog
if response.clicked() {
let dialog = rfd::FileDialog::new()
.add_filter("All Supported", &["gif", "png", "jpg", "jpeg", "webp", "mp4", "mov", "webm", "mkv", "avi", "m4v"])
.add_filter("Images", &["gif", "png", "jpg", "jpeg", "webp"])
.add_filter("Videos", &["mp4", "mov", "webm", "mkv", "avi", "m4v"]);
if let Some(paths) = dialog.pick_files() {
on_files_dropped(paths);
}
}
}
}