feat(mm): Initial commit with a basic image viewer
This commit is contained in:
21
src/cli.rs
Normal file
21
src/cli.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, clap::Parser)]
|
||||
pub struct Cli {
|
||||
#[clap(default_value = ".")]
|
||||
pub input: PathBuf,
|
||||
#[clap(long, exclusive(true))]
|
||||
pub completions: Option<clap_complete::Shell>,
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
pub fn completions(shell: clap_complete::Shell) {
|
||||
let mut command = <Cli as clap::CommandFactory>::command();
|
||||
clap_complete::generate(
|
||||
shell,
|
||||
&mut command,
|
||||
env!("CARGO_BIN_NAME"),
|
||||
&mut std::io::stdout(),
|
||||
);
|
||||
}
|
||||
}
|
||||
6
src/errors.rs
Normal file
6
src/errors.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
pub use error_stack::{Report, ResultExt};
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("An error occurred")]
|
||||
pub struct Error;
|
||||
|
||||
pub type Result<T, E = error_stack::Report<Error>> = core::result::Result<T, E>;
|
||||
39
src/main.rs
Normal file
39
src/main.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
mod cli;
|
||||
mod errors;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use errors::*;
|
||||
mod viewer;
|
||||
pub fn main() -> Result<()> {
|
||||
let args = <cli::Cli as clap::Parser>::parse();
|
||||
if let Some(shell) = args.completions {
|
||||
cli::Cli::completions(shell);
|
||||
return Ok(());
|
||||
}
|
||||
let files = walker(args.input);
|
||||
if files.is_empty() {
|
||||
return Err(Error).attach("No files found");
|
||||
}
|
||||
viewer::run(files);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Returns all the children (if a dir) and sister (if an image) files of the input path
|
||||
fn walker(input: impl AsRef<Path>) -> Vec<PathBuf> {
|
||||
let mut tb = ignore::types::TypesBuilder::new();
|
||||
tb.add("image", "*.jpg").expect("Failed to add image type");
|
||||
ignore::WalkBuilder::new(input)
|
||||
.types(
|
||||
tb.select("image")
|
||||
.add_defaults()
|
||||
.build()
|
||||
.expect("Failed to build type finder"),
|
||||
)
|
||||
.sort_by_file_name(|a, b| a.cmp(b))
|
||||
.build()
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| e.file_type().map_or(false, |ft| ft.is_file()))
|
||||
.map(|e| e.path().to_path_buf())
|
||||
.collect()
|
||||
}
|
||||
90
src/viewer.rs
Normal file
90
src/viewer.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
use gpui::{
|
||||
App, Application, Bounds, Context, KeyBinding, SharedString, Window, WindowBounds,
|
||||
WindowOptions, actions, div, img, prelude::*, px, rgb, rgba, size,
|
||||
};
|
||||
use nalgebra::Vector2;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct MMViewer {
|
||||
files: Vec<PathBuf>,
|
||||
current: usize,
|
||||
zoom: f32,
|
||||
pan: Vector2<f32>,
|
||||
}
|
||||
|
||||
actions!(mm, [Quit, NextImage, PrevImage]);
|
||||
|
||||
impl Render for MMViewer {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.size_full()
|
||||
.flex()
|
||||
.on_action(cx.listener(Self::next_image))
|
||||
.on_action(cx.listener(Self::prev_image))
|
||||
.flex_col()
|
||||
.justify_center()
|
||||
.bg(rgb(0x505050))
|
||||
.child(if let Some(file) = self.files.get(self.current) {
|
||||
div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.size_full()
|
||||
.justify_center()
|
||||
.child(img(file.clone()).size_full())
|
||||
} else {
|
||||
div().child(format!(
|
||||
"No image found (index: {}, total: {})",
|
||||
self.current,
|
||||
self.files.len()
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl MMViewer {
|
||||
fn next_image(&mut self, _: &NextImage, _: &mut Window, cx: &mut Context<Self>) {
|
||||
dbg!("aasdfasdf");
|
||||
if self.current + 1 < self.files.len() {
|
||||
self.current += 1;
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
fn prev_image(&mut self, _: &PrevImage, _: &mut Window, cx: &mut Context<Self>) {
|
||||
dbg!("aasdfascawsdfasdfdf");
|
||||
if self.current > 0 {
|
||||
self.current -= 1;
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(files: Vec<PathBuf>) {
|
||||
Application::new().run(|cx: &mut App| {
|
||||
let bounds = Bounds::centered(None, size(px(800f32), px(600f32)), cx);
|
||||
cx.on_action(|_: &Quit, cx| cx.quit());
|
||||
cx.bind_keys([
|
||||
KeyBinding::new("q", Quit, None),
|
||||
KeyBinding::new("Escape", Quit, None),
|
||||
KeyBinding::new("j", NextImage, None),
|
||||
KeyBinding::new("k", PrevImage, None),
|
||||
]);
|
||||
cx.open_window(
|
||||
WindowOptions {
|
||||
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
||||
// bounds: WindowBounds::Fixed(bounds),
|
||||
..Default::default()
|
||||
},
|
||||
|_, cx| {
|
||||
cx.new(|_| MMViewer {
|
||||
files,
|
||||
current: 0,
|
||||
zoom: 1.0,
|
||||
pan: Vector2::new(0.0, 0.0),
|
||||
})
|
||||
},
|
||||
)
|
||||
.expect("Failed to open window");
|
||||
cx.activate(true);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user