Compare commits

2 Commits
master ... anim

Author SHA1 Message Date
uttarayan21
22de9274b1 fix(viewer): remove unnecessary debug method call in layout setup 2025-10-10 10:15:00 +05:30
uttarayan21
9384b50397 feat(viewer): add bounding-box and animated SVG loading spinner 2025-10-09 13:35:56 +05:30
5 changed files with 165 additions and 34 deletions

62
Cargo.lock generated
View File

@@ -677,6 +677,22 @@ dependencies = [
"piper", "piper",
] ]
[[package]]
name = "bounding-box"
version = "0.1.0"
source = "git+https://github.com/aftershootco/ndcv-bridge#16c3b2e9105d3528a6ffbc2e1fe9046cb6930a6e"
dependencies = [
"color",
"itertools 0.14.0",
"nalgebra",
"ndarray",
"num",
"ordered-float",
"simba",
"thiserror 2.0.17",
"tracing",
]
[[package]] [[package]]
name = "bstr" name = "bstr"
version = "1.12.0" version = "1.12.0"
@@ -983,6 +999,12 @@ dependencies = [
"unicode-width", "unicode-width",
] ]
[[package]]
name = "color"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a18ef4657441fb193b65f34dc39b3781f0dfec23d3bd94d0eeb4e88cde421edb"
[[package]] [[package]]
name = "color_quant" name = "color_quant"
version = "1.1.0" version = "1.1.0"
@@ -3153,6 +3175,7 @@ name = "mm"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"ash", "ash",
"bounding-box",
"clap", "clap",
"clap_complete", "clap_complete",
"error-stack", "error-stack",
@@ -3255,6 +3278,21 @@ dependencies = [
"getrandom 0.2.16", "getrandom 0.2.16",
] ]
[[package]]
name = "ndarray"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841"
dependencies = [
"matrixmultiply",
"num-complex",
"num-integer",
"num-traits",
"portable-atomic",
"portable-atomic-util",
"rawpointer",
]
[[package]] [[package]]
name = "new_debug_unreachable" name = "new_debug_unreachable"
version = "1.0.6" version = "1.0.6"
@@ -3600,6 +3638,15 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "ordered-float"
version = "5.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d"
dependencies = [
"num-traits",
]
[[package]] [[package]]
name = "ordered-stream" name = "ordered-stream"
version = "0.2.0" version = "0.2.0"
@@ -3787,6 +3834,21 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5da3b0203fd7ee5720aa0b5e790b591aa5d3f41c3ed2c34a3a393382198af2f7" checksum = "5da3b0203fd7ee5720aa0b5e790b591aa5d3f41c3ed2c34a3a393382198af2f7"
[[package]]
name = "portable-atomic"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
[[package]]
name = "portable-atomic-util"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
dependencies = [
"portable-atomic",
]
[[package]] [[package]]
name = "postage" name = "postage"
version = "0.5.0" version = "0.5.0"

View File

@@ -14,10 +14,14 @@ thiserror = "2.0"
tokio = "1.43.1" tokio = "1.43.1"
tracing = "0.1" tracing = "0.1"
tracing-subscriber = "0.3" tracing-subscriber = "0.3"
gpui = { git = "https://github.com/uttarayan21/zed", default-features = false, features = ["wayland"] } gpui = { git = "https://github.com/uttarayan21/zed", default-features = false, features = [
"wayland",
] }
nalgebra = "0.34.1" nalgebra = "0.34.1"
wayland-sys = { version = "0.31.7", default-features = false } wayland-sys = { version = "0.31.7", default-features = false }
wayland-backend = { version = "0.3.11", default-features = false } wayland-backend = { version = "0.3.11", default-features = false }
ignore = { version = "0.4.23", features = ["simd-accel"] } ignore = { version = "0.4.23", features = ["simd-accel"] }
unicode-segmentation = "1.12.0" unicode-segmentation = "1.12.0"
ash = { version = "0.38.0", features = ["linked"] } ash = { version = "0.38.0", features = ["linked"] }
bounding-box = { git = "https://github.com/aftershootco/ndcv-bridge" }

6
assets/arrow_circle.svg Normal file
View File

@@ -0,0 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 8C3 6.67392 3.52678 5.40215 4.46446 4.46447C5.40214 3.52679 6.67391 3.00001 7.99999 3.00001C9.39779 3.00527 10.7394 3.55069 11.7444 4.52223L13 5.77778" stroke="black" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13 3.00001V5.77778H10.2222" stroke="black" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13 8C13 9.32608 12.4732 10.5978 11.5355 11.5355C10.5978 12.4732 9.32607 13 7.99999 13C6.60219 12.9947 5.26054 12.4493 4.25555 11.4778L3 10.2222" stroke="black" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.77777 10.2222H3V13" stroke="black" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 748 B

View File

@@ -18,11 +18,11 @@ pub fn main() -> Result<()> {
.change_context(Error) .change_context(Error)
.attach("Failed to canonicalize path")?; .attach("Failed to canonicalize path")?;
let files = walker(&input); let files = walker(&input);
if files.is_empty() { // if files.is_empty() {
return Err(Error) // return Err(Error)
.attach("No files found in the folder") // .attach("No files found in the folder")
.attach(input.display().to_string()); // .attach(input.display().to_string());
} // }
viewer::run(files); viewer::run(files);
Ok(()) Ok(())

View File

@@ -1,21 +1,54 @@
use gpui::{ use gpui::{
App, Application, Bounds, Context, FocusHandle, KeyBinding, ObjectFit, Window, WindowBounds, Animation, AnimationExt, App, Application, Bounds, Canvas, Context, FocusHandle, KeyBinding,
WindowOptions, actions, div, img, prelude::*, px, rgb, size, ObjectFit, Transformation, Window, WindowBounds, WindowOptions, actions, black, bounce, canvas,
div, ease_in_out, img, percentage, prelude::*, pulsating_between, px, rgb, rgba, size, svg,
}; };
use nalgebra::Vector2; use nalgebra::Vector2;
use std::path::PathBuf; use std::{path::PathBuf, time::Duration};
struct MMViewer { struct MMViewer {
files: Vec<PathBuf>, files: Vec<PathBuf>,
current: usize, current: usize,
zoom: f32, last: Option<usize>,
pan: Vector2<f32>, // zoom: f32,
// pan: Vector2<f32>,
focus: FocusHandle, focus: FocusHandle,
fit: ObjectFit, fit: ObjectFit,
} }
actions!(mm, [Quit, NextImage, PrevImage, FirstImage, LastImage, Fit]); actions!(mm, [Quit, NextImage, PrevImage, FirstImage, LastImage, Fit]);
#[derive(Debug, Clone)]
pub struct BoundingBox {
inner: bounding_box::Aabb2<f32>,
pub width: gpui::Pixels,
pub color: gpui::Rgba,
}
impl From<bounding_box::Aabb2<f32>> for BoundingBox {
fn from(aabb: bounding_box::Aabb2<f32>) -> Self {
Self {
inner: aabb,
width: px(2.0),
color: gpui::green().to_rgb(),
}
}
}
// impl Render for BoundingBox {
// fn render(&mut self, window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
// let mut builder = gpui::PathBuilder::stroke(self.width);
// let aabb = self.inner;
// builder.move_to(gpui::Point::new(px(aabb.x1()), px(aabb.y1())));
// builder.line_to(gpui::Point::new(px(aabb.x2()), px(aabb.y1())));
// builder.line_to(gpui::Point::new(px(aabb.x2()), px(aabb.y2())));
// builder.line_to(gpui::Point::new(px(aabb.x1()), px(aabb.y2())));
// builder.line_to(gpui::Point::new(px(aabb.x1()), px(aabb.y1())));
// let path = builder.build().expect("Failed to build path");
// window.paint_path(path, gpui::green())
// }
// }
impl Render for MMViewer { impl Render for MMViewer {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div() div()
@@ -31,34 +64,59 @@ impl Render for MMViewer {
.size_full() .size_full()
.flex_col() .flex_col()
.justify_center() .justify_center()
.bg(rgb(0x505050)) .bg(rgba(0xffffffff))
.child(if let Some(file) = self.files.get(self.current) { .child(if let Some(file) = self.files.get(self.current) {
div().flex().flex_row().size_full().justify_center().child( div()
img(file.clone()) .flex()
.object_fit(self.fit()) .flex_row()
.size_full() .size_full()
.with_loading(|| { .justify_center()
div() .child(
.flex() img(file.clone())
.flex_row() .id("loupe")
.size_full() .object_fit(self.fit())
.child("Loading...") .size_full()
.bg(rgb(0xffffff)) .with_loading(|| Self::loading().into_any_element()),
.justify_center() )
.into_any() .relative()
}), .into_any()
)
} else { } else {
div().child(format!( Self::loading().into_any_element()
"No image found (index: {}, total: {})", // div().child(format!(
self.current, // "No image found (index: {}, total: {})",
self.files.len() // self.current,
)) // self.files.len()
// ))
}) })
} }
} }
const ARROW_CIRCLE_SVG: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/arrow_circle.svg");
fn wheel() -> impl IntoElement {
svg()
.path(ARROW_CIRCLE_SVG)
.flex_none()
.size_full()
.text_color(black())
.with_animation(
"wheel",
Animation::new(Duration::from_secs(2))
.repeat()
.with_easing(bounce(ease_in_out)),
move |svg, delta| svg.with_transformation(Transformation::rotate(percentage(delta))),
)
}
impl MMViewer { impl MMViewer {
fn loading() -> impl IntoElement {
div()
.size_full()
.flex_none()
.bg(rgb(0xff00ff))
.child(wheel())
}
fn focus_handle(&self, _: &App) -> FocusHandle { fn focus_handle(&self, _: &App) -> FocusHandle {
self.focus.clone() self.focus.clone()
} }
@@ -143,8 +201,9 @@ pub fn run(files: Vec<PathBuf>) {
cx.new(|cx| MMViewer { cx.new(|cx| MMViewer {
files, files,
current: 0, current: 0,
zoom: 1.0, last: None,
pan: Vector2::new(0.0, 0.0), // zoom: 1.0,
// pan: Vector2::new(0.0, 0.0),
focus: cx.focus_handle(), focus: cx.focus_handle(),
fit: ObjectFit::Contain, fit: ObjectFit::Contain,
}) })