zoom/pan, path option, heic WIP
This commit is contained in:
parent
f55424b98f
commit
ed66f17139
111
src/app.rs
111
src/app.rs
@ -5,6 +5,7 @@ use egui_wgpu::wgpu::SurfaceError;
|
||||
use egui_wgpu::{ScreenDescriptor, wgpu};
|
||||
use imflow::store::ImageStore;
|
||||
use std::any::Any;
|
||||
use std::path::PathBuf;
|
||||
use std::process::exit;
|
||||
use std::sync::Arc;
|
||||
use std::time;
|
||||
@ -17,12 +18,39 @@ use winit::event_loop::ActiveEventLoop;
|
||||
use winit::platform::x11::WindowAttributesExtX11;
|
||||
use winit::window::{Window, WindowId};
|
||||
|
||||
// Uniforms for transformations
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct Transforms {
|
||||
transform: [f32; 16], // 4x4 matrix
|
||||
}
|
||||
|
||||
struct TransformData {
|
||||
pan_x: f32,
|
||||
pan_y: f32,
|
||||
zoom: f32,
|
||||
}
|
||||
|
||||
fn create_transform_matrix(data: &TransformData) -> [f32; 16] {
|
||||
const ZOOM_MULTIPLIER: f32 = 3.0;
|
||||
let zoom = data.zoom.powf(ZOOM_MULTIPLIER);
|
||||
[
|
||||
zoom, 0.0, 0.0, 0.0, 0.0, zoom, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, data.pan_x, data.pan_y, 0.0,
|
||||
1.0,
|
||||
]
|
||||
}
|
||||
|
||||
fn setup_texture(
|
||||
device: &wgpu::Device,
|
||||
surface_config: SurfaceConfiguration,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> (wgpu::Texture, wgpu::BindGroup, wgpu::RenderPipeline) {
|
||||
) -> (
|
||||
wgpu::Texture,
|
||||
wgpu::BindGroup,
|
||||
wgpu::RenderPipeline,
|
||||
wgpu::Buffer,
|
||||
) {
|
||||
// Create your texture (one-time setup)
|
||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("Image texture"),
|
||||
@ -71,9 +99,26 @@ fn setup_texture(
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let transform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("Transform Uniform Buffer"),
|
||||
size: std::mem::size_of::<Transforms>() as u64,
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
// Create bind group with your texture
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("Texture Bind Group"),
|
||||
@ -87,6 +132,10 @@ fn setup_texture(
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(&sampler),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 2,
|
||||
resource: transform_buffer.as_entire_binding(),
|
||||
},
|
||||
],
|
||||
});
|
||||
// Define vertex buffer layout
|
||||
@ -151,7 +200,7 @@ fn setup_texture(
|
||||
cache: None,
|
||||
});
|
||||
|
||||
(texture, bind_group, render_pipeline)
|
||||
(texture, bind_group, render_pipeline, transform_buffer)
|
||||
}
|
||||
|
||||
pub struct AppState {
|
||||
@ -165,6 +214,8 @@ pub struct AppState {
|
||||
pub image_texture: wgpu::Texture,
|
||||
pub bind_group: wgpu::BindGroup,
|
||||
pub render_pipeline: wgpu::RenderPipeline,
|
||||
pub transform_buffer: wgpu::Buffer,
|
||||
pub transform_data: TransformData,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
@ -174,6 +225,7 @@ impl AppState {
|
||||
window: &Window,
|
||||
width: u32,
|
||||
height: u32,
|
||||
path: PathBuf
|
||||
) -> Self {
|
||||
let power_pref = wgpu::PowerPreference::default();
|
||||
let adapter = instance
|
||||
@ -224,11 +276,17 @@ impl AppState {
|
||||
|
||||
let scale_factor = 1.0;
|
||||
|
||||
let store = ImageStore::new("./test_images".into());
|
||||
let store = ImageStore::new(path);
|
||||
|
||||
let (image_texture, bind_group, render_pipeline) =
|
||||
let (image_texture, bind_group, render_pipeline, transform_buffer) =
|
||||
setup_texture(&device, surface_config.clone(), 6000, 4000);
|
||||
|
||||
let transform_data = TransformData {
|
||||
pan_x: 0.0,
|
||||
pan_y: 0.0,
|
||||
zoom: 1.0,
|
||||
};
|
||||
|
||||
Self {
|
||||
device,
|
||||
queue,
|
||||
@ -240,6 +298,8 @@ impl AppState {
|
||||
image_texture,
|
||||
bind_group,
|
||||
render_pipeline,
|
||||
transform_buffer,
|
||||
transform_data,
|
||||
}
|
||||
}
|
||||
|
||||
@ -254,15 +314,17 @@ pub struct App {
|
||||
instance: wgpu::Instance,
|
||||
state: Option<AppState>,
|
||||
window: Option<Arc<Window>>,
|
||||
path: PathBuf
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(path: PathBuf) -> Self {
|
||||
let instance = egui_wgpu::wgpu::Instance::new(&wgpu::InstanceDescriptor::default());
|
||||
Self {
|
||||
instance,
|
||||
state: None,
|
||||
window: None,
|
||||
path
|
||||
}
|
||||
}
|
||||
|
||||
@ -284,12 +346,14 @@ impl App {
|
||||
&window,
|
||||
initial_width,
|
||||
initial_width,
|
||||
self.path.clone()
|
||||
)
|
||||
.await;
|
||||
|
||||
self.window.get_or_insert(window);
|
||||
self.state.get_or_insert(state);
|
||||
|
||||
self.pan_zoom(0.0, 0.0, 0.0);
|
||||
self.update_texture();
|
||||
}
|
||||
|
||||
@ -340,6 +404,19 @@ impl App {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn pan_zoom(&mut self, zoom_delta: f32, pan_x: f32, pan_y: f32) {
|
||||
let state = self.state.as_mut().unwrap();
|
||||
state.transform_data.zoom = (state.transform_data.zoom + zoom_delta).clamp(1.0, 20.0);
|
||||
state.transform_data.pan_x += pan_x;
|
||||
state.transform_data.pan_y += pan_y;
|
||||
let transform = create_transform_matrix(&state.transform_data);
|
||||
state.queue.write_buffer(
|
||||
&state.transform_buffer,
|
||||
0,
|
||||
bytemuck::cast_slice(&[Transforms { transform }]),
|
||||
);
|
||||
}
|
||||
|
||||
fn handle_redraw(&mut self) {
|
||||
// Attempt to handle minimizing window
|
||||
if let Some(window) = self.window.as_ref() {
|
||||
@ -489,6 +566,8 @@ impl App {
|
||||
}
|
||||
|
||||
let rating = state.store.get_current_rating();
|
||||
let path = state.store.current_image_path.clone();
|
||||
let filename = path.file_name().unwrap();
|
||||
let window = self.window.as_ref().unwrap();
|
||||
{
|
||||
state.egui_renderer.begin_frame(window);
|
||||
@ -504,6 +583,11 @@ impl App {
|
||||
.size(42.0)
|
||||
.strong(),
|
||||
);
|
||||
ui.label(
|
||||
egui::RichText::new(format!("{}", filename.to_str().unwrap()))
|
||||
.size(10.0)
|
||||
.strong(),
|
||||
);
|
||||
// ui.add_space(10.0);
|
||||
});
|
||||
});
|
||||
@ -550,15 +634,13 @@ impl ApplicationHandler for App {
|
||||
self.handle_redraw();
|
||||
// println!("Updated in: {}ms", start.elapsed().as_millis());
|
||||
// Extract the events by cloning them from the input context
|
||||
let events = self
|
||||
let (events, keys_down) = self
|
||||
.state
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.egui_renderer
|
||||
.context()
|
||||
.input(|i| {
|
||||
i.events.clone() // Clone the events to own them outside the closure
|
||||
});
|
||||
.input(|i| (i.events.clone(), i.keys_down.clone()));
|
||||
|
||||
// Now use the extracted events outside the closure
|
||||
events.iter().for_each(|e| {
|
||||
@ -595,6 +677,17 @@ impl ApplicationHandler for App {
|
||||
Key::Escape => exit(0),
|
||||
_ => {}
|
||||
}
|
||||
} else if let Event::MouseWheel {
|
||||
unit,
|
||||
delta,
|
||||
modifiers,
|
||||
} = e
|
||||
{
|
||||
self.pan_zoom(delta.y * 0.2, 0.0, 0.0);
|
||||
} else if let Event::PointerMoved(pos) = e {
|
||||
if keys_down.contains(&Key::Tab) {
|
||||
self.pan_zoom(0.0, pos.x * 0.00001, pos.y * 0.00001);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
16
src/image.rs
16
src/image.rs
@ -18,7 +18,7 @@ pub struct ImflowImageBuffer {
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
pub argb_buffer: Vec<u32>,
|
||||
pub rating: i32
|
||||
pub rating: i32,
|
||||
}
|
||||
|
||||
pub fn create_iced_handle(width: u32, height: u32, rgba: Vec<u8>) -> Handle {
|
||||
@ -81,7 +81,7 @@ pub fn load_image(path: PathBuf) -> ImflowImageBuffer {
|
||||
width,
|
||||
height,
|
||||
argb_buffer: buffer_u32,
|
||||
rating
|
||||
rating,
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,7 +103,7 @@ pub fn load_available_images(dir: PathBuf) -> Vec<PathBuf> {
|
||||
let mut files: Vec<PathBuf> = fs::read_dir(dir)
|
||||
.unwrap()
|
||||
.map(|f| f.unwrap().path())
|
||||
.filter(|f| f.extension().unwrap().to_ascii_lowercase() == "jpg")
|
||||
.filter(|f| ["jpg", "heic"].contains(&f.extension().unwrap().to_ascii_lowercase().to_str().unwrap()))
|
||||
.collect();
|
||||
files.sort();
|
||||
files
|
||||
@ -113,8 +113,10 @@ pub fn get_embedded_thumbnail(path: PathBuf) -> Option<Vec<u8>> {
|
||||
let meta = rexiv2::Metadata::new_from_path(path);
|
||||
match meta {
|
||||
Ok(meta) => {
|
||||
for preview in meta.get_preview_images().unwrap() {
|
||||
return Some(preview.get_data().unwrap());
|
||||
if let Some(previews) = meta.get_preview_images() {
|
||||
for preview in previews {
|
||||
return Some(preview.get_data().unwrap());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
@ -155,7 +157,7 @@ pub fn load_thumbnail_exif(path: &PathBuf) -> Option<ImflowImageBuffer> {
|
||||
width,
|
||||
height,
|
||||
argb_buffer: buffer,
|
||||
rating
|
||||
rating,
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
@ -180,6 +182,6 @@ pub fn load_thumbnail_full(path: &PathBuf) -> ImflowImageBuffer {
|
||||
width,
|
||||
height,
|
||||
argb_buffer: buffer,
|
||||
rating
|
||||
rating,
|
||||
}
|
||||
}
|
||||
|
@ -323,18 +323,21 @@ mod egui_tools;
|
||||
use winit::event_loop::{ControlFlow, EventLoop};
|
||||
|
||||
fn main() {
|
||||
|
||||
let args = Args::parse();
|
||||
let path = args.path.unwrap_or("./test_images".into());
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
pollster::block_on(run());
|
||||
pollster::block_on(run(path));
|
||||
}
|
||||
}
|
||||
|
||||
async fn run() {
|
||||
async fn run(path: PathBuf) {
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
|
||||
event_loop.set_control_flow(ControlFlow::Poll);
|
||||
|
||||
let mut app = app::App::new();
|
||||
let mut app = app::App::new(path);
|
||||
|
||||
event_loop.run_app(&mut app).expect("Failed to run app");
|
||||
|
||||
|
@ -1,3 +1,8 @@
|
||||
struct Transforms {
|
||||
transform: mat4x4<f32>,
|
||||
};
|
||||
@group(0) @binding(2) var<uniform> transforms: Transforms;
|
||||
|
||||
struct VertexInput {
|
||||
@location(0) position: vec3<f32>,
|
||||
@location(1) uv: vec2<f32>,
|
||||
@ -11,7 +16,8 @@ struct VertexOutput {
|
||||
@vertex
|
||||
fn vs_main(in: VertexInput) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
out.position = vec4<f32>(in.position, 1.0);
|
||||
// Apply zoom/transformation matrix
|
||||
out.position = transforms.transform * vec4<f32>(in.position, 1.0);
|
||||
out.uv = in.uv;
|
||||
return out;
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ pub struct ImageStore {
|
||||
pub(crate) loaded_images: HashMap<PathBuf, ImflowImageBuffer>,
|
||||
pub(crate) loaded_images_thumbnails: HashMap<PathBuf, ImflowImageBuffer>,
|
||||
pub(crate) available_images: Vec<PathBuf>,
|
||||
pub(crate) current_image_path: PathBuf,
|
||||
pub current_image_path: PathBuf,
|
||||
pub(crate) pool: ThreadPool,
|
||||
pub(crate) loader_rx: mpsc::Receiver<(PathBuf, ImflowImageBuffer)>,
|
||||
pub(crate) loader_tx: mpsc::Sender<(PathBuf, ImflowImageBuffer)>,
|
||||
|
Loading…
x
Reference in New Issue
Block a user