From f55424b98f218977f5548d55c382edc8cbc916f3 Mon Sep 17 00:00:00 2001 From: Dawid Pietrykowski Date: Sat, 5 Apr 2025 22:57:03 +0200 Subject: [PATCH] Move to egui + winit, add rating --- Cargo.lock | 325 +------------------------ Cargo.toml | 15 +- src/app.rs | 609 ++++++++++++++++++++++++++++++++++++++++++++++ src/egui_tools.rs | 118 +++++++++ src/image.rs | 47 +++- src/main.rs | 260 +++++++++++++------- src/shader.wgsl | 25 ++ src/store.rs | 35 ++- 8 files changed, 1015 insertions(+), 419 deletions(-) create mode 100644 src/app.rs create mode 100644 src/egui_tools.rs create mode 100644 src/shader.wgsl diff --git a/Cargo.lock b/Cargo.lock index 412e197..5f4f61b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -597,18 +597,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - [[package]] name = "bit-set" version = "0.5.3" @@ -1269,12 +1257,6 @@ dependencies = [ "zbus", ] -[[package]] -name = "data-url" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" - [[package]] name = "dconf_rs" version = "0.3.0" @@ -1511,23 +1493,6 @@ dependencies = [ "winit", ] -[[package]] -name = "egui_extras" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624659a2e972a46f4d5f646557906c55f1cd5a0836eddbe610fdf1afba1b4226" -dependencies = [ - "ahash 0.8.11", - "egui", - "ehttp", - "enum-map", - "image 0.25.6", - "log", - "mime_guess2", - "profiling", - "resvg", -] - [[package]] name = "egui_glow" version = "0.31.1" @@ -1546,20 +1511,6 @@ dependencies = [ "winit", ] -[[package]] -name = "ehttp" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a81c221a1e4dad06cb9c9deb19aea1193a5eea084e8cd42d869068132bf876" -dependencies = [ - "document-features", - "js-sys", - "ureq", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "either" version = "1.15.0" @@ -1581,27 +1532,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" -[[package]] -name = "enum-map" -version = "2.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9" -dependencies = [ - "enum-map-derive", - "serde", -] - -[[package]] -name = "enum-map-derive" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - [[package]] name = "enumflags2" version = "0.7.11" @@ -1778,12 +1708,6 @@ dependencies = [ "miniz_oxide", ] -[[package]] -name = "float-cmp" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" - [[package]] name = "float_next_after" version = "1.0.0" @@ -1811,7 +1735,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1fcfcd44ca6e90c921fee9fa665d530b21ef1327a4c1a6c5250ea44b776ada7" dependencies = [ - "roxmltree 0.20.0", + "roxmltree", ] [[package]] @@ -2474,7 +2398,7 @@ dependencies = [ "bytemuck", "cosmic-text", "iced_graphics", - "kurbo 0.10.4", + "kurbo", "log", "rustc-hash 2.1.1", "softbuffer", @@ -2727,21 +2651,17 @@ dependencies = [ "quick-error", ] -[[package]] -name = "imagesize" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" - [[package]] name = "imflow" version = "0.1.0" dependencies = [ + "bytemuck", "clap 4.5.34", "criterion", "eframe", "egui", - "egui_extras", + "egui-wgpu", + "egui-winit", "env_logger", "iced", "image 0.25.6", @@ -3061,15 +2981,6 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" -[[package]] -name = "kurbo" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd85a5776cd9500c2e2059c8c76c3b01528566b7fcbaf8098b55a33fc298849b" -dependencies = [ - "arrayvec", -] - [[package]] name = "kurbo" version = "0.10.4" @@ -3340,22 +3251,6 @@ dependencies = [ "paste", ] -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "mime_guess2" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25a3333bb1609500601edc766a39b4c1772874a4ce26022f4d866854dc020c41" -dependencies = [ - "mime", - "unicase", -] - [[package]] name = "minifb" version = "0.28.0" @@ -4080,15 +3975,9 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ - "siphasher 1.0.1", + "siphasher", ] -[[package]] -name = "pico-args" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" - [[package]] name = "pin-project" version = "1.1.10" @@ -4434,12 +4323,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "rctree" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b42e27ef78c35d3998403c1d26f3efd9e135d3e5121b0a4845cc5cc27547f4f" - [[package]] name = "read-fonts" version = "0.22.7" @@ -4523,20 +4406,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" -[[package]] -name = "resvg" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cadccb3d99a9efb8e5e00c16fbb732cbe400db2ec7fc004697ee7d97d86cf1f4" -dependencies = [ - "log", - "pico-args", - "rgb", - "svgtypes", - "tiny-skia", - "usvg", -] - [[package]] name = "rexiv2" version = "0.10.0" @@ -4553,29 +4422,6 @@ name = "rgb" version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.15", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "roxmltree" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f" [[package]] name = "roxmltree" @@ -4637,38 +4483,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "rustls" -version = "0.23.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c" -dependencies = [ - "log", - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pki-types" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" - -[[package]] -name = "rustls-webpki" -version = "0.103.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - [[package]] name = "rustversion" version = "1.0.20" @@ -4873,21 +4687,6 @@ dependencies = [ "quote", ] -[[package]] -name = "simplecss" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" -dependencies = [ - "log", -] - -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - [[package]] name = "siphasher" version = "1.0.1" @@ -5031,9 +4830,6 @@ name = "strict-num" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" -dependencies = [ - "float-cmp", -] [[package]] name = "strsim" @@ -5063,28 +4859,12 @@ dependencies = [ "syn 2.0.100", ] -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - [[package]] name = "svg_fmt" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce5d813d71d82c4cbc1742135004e4a79fd870214c155443451c139c9470a0aa" -[[package]] -name = "svgtypes" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e44e288cd960318917cbd540340968b90becc8bc81f171345d706e7a89d9d70" -dependencies = [ - "kurbo 0.9.5", - "siphasher 0.3.11", -] - [[package]] name = "swash" version = "0.1.19" @@ -5477,12 +5257,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "unicase" -version = "2.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" - [[package]] name = "unicode-bidi" version = "0.3.18" @@ -5543,28 +5317,6 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "ureq" -version = "2.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" -dependencies = [ - "base64 0.22.1", - "flate2", - "log", - "once_cell", - "rustls", - "rustls-pki-types", - "url", - "webpki-roots", -] - [[package]] name = "url" version = "2.5.4" @@ -5576,50 +5328,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "usvg" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b0a51b72ab80ca511d126b77feeeb4fb1e972764653e61feac30adc161a756" -dependencies = [ - "base64 0.21.7", - "log", - "pico-args", - "usvg-parser", - "usvg-tree", - "xmlwriter", -] - -[[package]] -name = "usvg-parser" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bd4e3c291f45d152929a31f0f6c819245e2921bfd01e7bd91201a9af39a2bdc" -dependencies = [ - "data-url", - "flate2", - "imagesize", - "kurbo 0.9.5", - "log", - "roxmltree 0.19.0", - "simplecss", - "siphasher 0.3.11", - "svgtypes", - "usvg-tree", -] - -[[package]] -name = "usvg-tree" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee3d202ebdb97a6215604b8f5b4d6ef9024efd623cf2e373a6416ba976ec7d3" -dependencies = [ - "rctree", - "strict-num", - "svgtypes", - "tiny-skia-path", -] - [[package]] name = "utf16_iter" version = "1.0.5" @@ -6005,15 +5713,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "webpki-roots" -version = "0.26.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "weezl" version = "0.1.8" @@ -6748,12 +6447,6 @@ version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" -[[package]] -name = "xmlwriter" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" - [[package]] name = "yazi" version = "0.1.6" @@ -6950,12 +6643,6 @@ dependencies = [ "synstructure", ] -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" - [[package]] name = "zerovec" version = "0.10.4" diff --git a/Cargo.toml b/Cargo.toml index 13e31d1..78aa2ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,10 +4,16 @@ version = "0.1.0" edition = "2024" [dependencies] +egui = "0.31.1" +egui-wgpu = { version = "0.31.1",features = ["winit"] } +egui-winit = "0.31.1" +winit = "0.30.9" +pollster = "0.4.0" + clap = { version = "4.5.34", features = ["derive"] } eframe = "0.31.1" -egui = "0.31.1" -egui_extras = { version = "0.31.1", features = ["all_loaders"] } +# egui = "0.31.1" +# egui_extras = { version = "0.31.1", features = ["all_loaders"] } env_logger = "0.11.7" iced = { version = "0.13.1", features = ["debug", "canvas", "tokio", "image"]} image = "0.25.6" @@ -15,15 +21,16 @@ image = "0.25.6" itertools = "0.12" memmap2 = "0.9.5" minifb = "0.28.0" -pollster = "0.4.0" +# pollster = "0.4.0" rexiv2 = "0.10.0" threadpool = "1.8.1" # rustc-hash.workspace = true tokio = { version = "1.44.1", features = ["sync"] } tracing-subscriber = "0.3" wgpu = "24.0.3" -winit = "0.30.9" +# winit = "0.30.9" zune-image = {version = "0.4.15", features = ["all"]} +bytemuck = "1.22.0" [profile.release] opt-level = 3 debug = false diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..4b41144 --- /dev/null +++ b/src/app.rs @@ -0,0 +1,609 @@ +use crate::egui_tools::EguiRenderer; +use eframe::WindowAttributes; +use egui::{Event, Key}; +use egui_wgpu::wgpu::SurfaceError; +use egui_wgpu::{ScreenDescriptor, wgpu}; +use imflow::store::ImageStore; +use std::any::Any; +use std::process::exit; +use std::sync::Arc; +use std::time; +use wgpu::util::DeviceExt; +use wgpu::{PipelineCompilationOptions, SurfaceConfiguration}; +use winit::application::ApplicationHandler; +use winit::dpi::{LogicalSize, PhysicalSize}; +use winit::event::WindowEvent; +use winit::event_loop::ActiveEventLoop; +use winit::platform::x11::WindowAttributesExtX11; +use winit::window::{Window, WindowId}; + +fn setup_texture( + device: &wgpu::Device, + surface_config: SurfaceConfiguration, + width: u32, + height: u32, +) -> (wgpu::Texture, wgpu::BindGroup, wgpu::RenderPipeline) { + // Create your texture (one-time setup) + let texture = device.create_texture(&wgpu::TextureDescriptor { + label: Some("Image texture"), + size: wgpu::Extent3d { + width, + height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + view_formats: &[], + }); + + // Create texture view and sampler + let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::FilterMode::Linear, + ..Default::default() + }); + + // Create bind group layout for the texture + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("Texture Bind Group Layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + }); + + // Create bind group with your texture + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("Texture Bind Group"), + layout: &bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&texture_view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&sampler), + }, + ], + }); + // Define vertex buffer layout + let vertex_buffer_layout = wgpu::VertexBufferLayout { + array_stride: 5 * std::mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[ + // Position + wgpu::VertexAttribute { + offset: 0, + shader_location: 0, + format: wgpu::VertexFormat::Float32x3, + }, + // UV + wgpu::VertexAttribute { + offset: 3 * std::mem::size_of::() as wgpu::BufferAddress, + shader_location: 1, + format: wgpu::VertexFormat::Float32x2, + }, + ], + }; + + // Create shader modules + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Texture Shader"), + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!("shader.wgsl"))), + }); + + // Create the render pipeline (simplified, you'd need to define vertex buffers, etc.) + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Texture Render Pipeline"), + layout: Some( + &device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Texture Pipeline Layout"), + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }), + ), + vertex: wgpu::VertexState { + module: &shader, + entry_point: Some("vs_main"), + buffers: &[vertex_buffer_layout], + compilation_options: PipelineCompilationOptions::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_main"), + targets: &[Some(wgpu::ColorTargetState { + format: surface_config.format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: PipelineCompilationOptions::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + cache: None, + }); + + (texture, bind_group, render_pipeline) +} + +pub struct AppState { + pub device: wgpu::Device, + pub queue: wgpu::Queue, + pub surface_config: wgpu::SurfaceConfiguration, + pub surface: wgpu::Surface<'static>, + pub scale_factor: f32, + pub egui_renderer: EguiRenderer, + pub store: ImageStore, + pub image_texture: wgpu::Texture, + pub bind_group: wgpu::BindGroup, + pub render_pipeline: wgpu::RenderPipeline, +} + +impl AppState { + async fn new( + instance: &wgpu::Instance, + surface: wgpu::Surface<'static>, + window: &Window, + width: u32, + height: u32, + ) -> Self { + let power_pref = wgpu::PowerPreference::default(); + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: power_pref, + force_fallback_adapter: false, + compatible_surface: Some(&surface), + }) + .await + .expect("Failed to find an appropriate adapter"); + + let features = wgpu::Features::empty(); + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + required_features: features, + required_limits: Default::default(), + memory_hints: Default::default(), + }, + None, + ) + .await + .expect("Failed to create device"); + + let swapchain_capabilities = surface.get_capabilities(&adapter); + let selected_format = wgpu::TextureFormat::Bgra8UnormSrgb; + let swapchain_format = swapchain_capabilities + .formats + .iter() + .find(|d| **d == selected_format) + .expect("failed to select proper surface texture format!"); + + let surface_config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: *swapchain_format, + width, + height, + present_mode: wgpu::PresentMode::AutoVsync, + desired_maximum_frame_latency: 0, + alpha_mode: swapchain_capabilities.alpha_modes[0], + view_formats: vec![], + }; + + surface.configure(&device, &surface_config); + + let egui_renderer = EguiRenderer::new(&device, surface_config.format, None, 1, window); + + let scale_factor = 1.0; + + let store = ImageStore::new("./test_images".into()); + + let (image_texture, bind_group, render_pipeline) = + setup_texture(&device, surface_config.clone(), 6000, 4000); + + Self { + device, + queue, + surface, + surface_config, + egui_renderer, + scale_factor, + store, + image_texture, + bind_group, + render_pipeline, + } + } + + fn resize_surface(&mut self, width: u32, height: u32) { + self.surface_config.width = width; + self.surface_config.height = height; + self.surface.configure(&self.device, &self.surface_config); + } +} + +pub struct App { + instance: wgpu::Instance, + state: Option, + window: Option>, +} + +impl App { + pub fn new() -> Self { + let instance = egui_wgpu::wgpu::Instance::new(&wgpu::InstanceDescriptor::default()); + Self { + instance, + state: None, + window: None, + } + } + + async fn set_window(&mut self, window: Window) { + let window = Arc::new(window); + let initial_width = 1500; + let initial_height = 1000; + + let _ = window.request_inner_size(PhysicalSize::new(initial_width, initial_height)); + + let surface = self + .instance + .create_surface(window.clone()) + .expect("Failed to create surface!"); + + let state = AppState::new( + &self.instance, + surface, + &window, + initial_width, + initial_width, + ) + .await; + + self.window.get_or_insert(window); + self.state.get_or_insert(state); + + self.update_texture(); + } + + fn handle_resized(&mut self, width: u32, height: u32) { + println!("Resized {} {}", width, height); + if width > 0 && height > 0 { + self.state.as_mut().unwrap().resize_surface(width, height); + } + } + + pub fn update_texture(&mut self) { + let state = self.state.as_mut().unwrap(); + + state.store.check_loaded_images(); + let imbuf = if let Some(full) = state.store.get_current_image() { + println!("full"); + full + } else { + state.store.get_thumbnail() + }; + let width = imbuf.width as u32; + let height = imbuf.height as u32; + let buffer_u8 = unsafe { + std::slice::from_raw_parts( + imbuf.argb_buffer.as_ptr() as *const u8, + imbuf.argb_buffer.len() * 4, + ) + }; + + state.queue.write_texture( + wgpu::TexelCopyTextureInfo { + texture: &state.image_texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + &buffer_u8, + wgpu::TexelCopyBufferLayout { + offset: 0, + bytes_per_row: Some(4 * width), // 4 bytes per ARGB pixel + rows_per_image: Some(height), + }, + wgpu::Extent3d { + width, + height, + depth_or_array_layers: 1, + }, + ); + } + + fn handle_redraw(&mut self) { + // Attempt to handle minimizing window + if let Some(window) = self.window.as_ref() { + if let Some(min) = window.is_minimized() { + if min { + println!("Window is minimized"); + return; + } + } + } + + let state = self.state.as_mut().unwrap(); + + let screen_descriptor = ScreenDescriptor { + size_in_pixels: [state.surface_config.width, state.surface_config.height], + pixels_per_point: self.window.as_ref().unwrap().scale_factor() as f32 + * state.scale_factor, + }; + + let surface_texture = state.surface.get_current_texture(); + + let surface_texture = match surface_texture { + Err(SurfaceError::Outdated) => { + // Ignoring outdated to allow resizing and minimization + println!("wgpu surface outdated"); + return; + } + Err(SurfaceError::Timeout) => { + println!("wgpu surface timeout"); + return; + } + Err(_) => { + surface_texture.expect("Failed to acquire next swap chain texture"); + return; + } + Ok(surface_texture) => surface_texture, + }; + + let surface_view = surface_texture + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + + let mut encoder = state + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + + // Add this render pass to clear the screen with green + { + let _ = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &surface_view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 0.0, + g: 1.0, // Green + b: 0.0, + a: 1.0, + }), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + } + + { + // Define vertices for your quad + #[repr(C)] + #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] + struct Vertex { + position: [f32; 3], + tex_coords: [f32; 2], + } + + // Define a quad (two triangles) + let vertices = [ + // Position (x, y, z), Texture coords (u, v) + Vertex { + position: [-1.0, -1.0, 0.0], + tex_coords: [0.0, 1.0], + }, // bottom left + Vertex { + position: [-1.0, 1.0, 0.0], + tex_coords: [0.0, 0.0], + }, // top left + Vertex { + position: [1.0, -1.0, 0.0], + tex_coords: [1.0, 1.0], + }, // bottom right + Vertex { + position: [1.0, 1.0, 0.0], + tex_coords: [1.0, 0.0], + }, // top right + ]; + + // Create indices for drawing two triangles + let indices: [u16; 6] = [0, 1, 2, 2, 1, 3]; + + // Create vertex buffer + let vertex_buffer = + state + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Vertex Buffer"), + contents: bytemuck::cast_slice(&vertices), + usage: wgpu::BufferUsages::VERTEX, + }); + + // Create index buffer + let index_buffer = state + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Index Buffer"), + contents: bytemuck::cast_slice(&indices), + usage: wgpu::BufferUsages::INDEX, + }); + + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Texture Render Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &surface_view, + resolve_target: None, + ops: wgpu::Operations { + // Use Load instead of Clear so we don't erase the green background + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + + render_pass.set_pipeline(&state.render_pipeline); + render_pass.set_bind_group(0, &state.bind_group, &[]); + + // Bind the vertex buffer + render_pass.set_vertex_buffer(0, vertex_buffer.slice(..)); + + // Draw using the index buffer (more efficient) + render_pass.set_index_buffer(index_buffer.slice(..), wgpu::IndexFormat::Uint16); + render_pass.draw_indexed(0..6, 0, 0..1); + } + + let rating = state.store.get_current_rating(); + let window = self.window.as_ref().unwrap(); + { + state.egui_renderer.begin_frame(window); + + egui::Window::new("Rating") + .collapsible(false) + .resizable(false) + .default_width(5.0) + .show(state.egui_renderer.context(), |ui| { + ui.vertical_centered(|ui| { + ui.label( + egui::RichText::new(format!("{:.1}", rating)) + .size(42.0) + .strong(), + ); + // ui.add_space(10.0); + }); + }); + + state.egui_renderer.end_frame_and_draw( + &state.device, + &state.queue, + &mut encoder, + window, + &surface_view, + screen_descriptor, + ); + } + + state.queue.submit(Some(encoder.finish())); + surface_texture.present(); + } +} + +impl ApplicationHandler for App { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + let attributes = Window::default_attributes() + .with_base_size(LogicalSize::new(2000, 4000)) + .with_resizable(false); + let window = event_loop.create_window(attributes).unwrap(); + pollster::block_on(self.set_window(window)); + } + + fn window_event(&mut self, event_loop: &ActiveEventLoop, _: WindowId, event: WindowEvent) { + // let egui render to process the event first + self.state + .as_mut() + .unwrap() + .egui_renderer + .handle_input(self.window.as_ref().unwrap(), &event); + + match event { + WindowEvent::CloseRequested => { + println!("The close button was pressed; stopping"); + event_loop.exit(); + } + WindowEvent::RedrawRequested => { + let start = time::Instant::now(); + self.handle_redraw(); + // println!("Updated in: {}ms", start.elapsed().as_millis()); + // Extract the events by cloning them from the input context + let events = self + .state + .as_ref() + .unwrap() + .egui_renderer + .context() + .input(|i| { + i.events.clone() // Clone the events to own them outside the closure + }); + + // Now use the extracted events outside the closure + events.iter().for_each(|e| { + if let Event::Key { key, pressed, .. } = e { + if !*pressed { + return; + } + match *key { + Key::ArrowLeft => { + self.state.as_mut().unwrap().store.next_image(-1); + self.update_texture(); + } + Key::ArrowRight => { + self.state.as_mut().unwrap().store.next_image(1); + self.update_texture(); + } + Key::ArrowUp => { + let rating = + self.state.as_mut().unwrap().store.get_current_rating(); + self.state.as_mut().unwrap().store.set_rating(rating + 1); + } + Key::ArrowDown => { + let rating = + self.state.as_mut().unwrap().store.get_current_rating(); + self.state.as_mut().unwrap().store.set_rating(rating - 1); + } + Key::Backtick => self.state.as_mut().unwrap().store.set_rating(0), + Key::Num0 => self.state.as_mut().unwrap().store.set_rating(0), + Key::Num1 => self.state.as_mut().unwrap().store.set_rating(1), + Key::Num2 => self.state.as_mut().unwrap().store.set_rating(2), + Key::Num3 => self.state.as_mut().unwrap().store.set_rating(3), + Key::Num4 => self.state.as_mut().unwrap().store.set_rating(4), + Key::Num5 => self.state.as_mut().unwrap().store.set_rating(5), + Key::Escape => exit(0), + _ => {} + } + } + }); + + self.window.as_ref().unwrap().request_redraw(); + } + WindowEvent::Resized(new_size) => { + self.handle_resized(new_size.width, new_size.height); + } + _ => (), + } + } +} diff --git a/src/egui_tools.rs b/src/egui_tools.rs new file mode 100644 index 0000000..d1c2a04 --- /dev/null +++ b/src/egui_tools.rs @@ -0,0 +1,118 @@ +use egui::Context; +use egui_wgpu::wgpu::{CommandEncoder, Device, Queue, StoreOp, TextureFormat, TextureView}; +use egui_wgpu::{wgpu, Renderer, ScreenDescriptor}; +use egui_winit::State; +use winit::event::WindowEvent; +use winit::window::Window; + +pub struct EguiRenderer { + state: State, + renderer: Renderer, + frame_started: bool, +} + +impl EguiRenderer { + pub fn context(&self) -> &Context { + self.state.egui_ctx() + } + + pub fn new( + device: &Device, + output_color_format: TextureFormat, + output_depth_format: Option, + msaa_samples: u32, + window: &Window, + ) -> EguiRenderer { + let egui_context = Context::default(); + + let egui_state = egui_winit::State::new( + egui_context, + egui::viewport::ViewportId::ROOT, + &window, + Some(window.scale_factor() as f32), + None, + Some(2 * 1024), // default dimension is 2048 + ); + let egui_renderer = Renderer::new( + device, + output_color_format, + output_depth_format, + msaa_samples, + true, + ); + + EguiRenderer { + state: egui_state, + renderer: egui_renderer, + frame_started: false, + } + } + + pub fn handle_input(&mut self, window: &Window, event: &WindowEvent) { + let _ = self.state.on_window_event(window, event); + } + + pub fn ppp(&mut self, v: f32) { + self.context().set_pixels_per_point(v); + } + + pub fn begin_frame(&mut self, window: &Window) { + let raw_input = self.state.take_egui_input(window); + self.state.egui_ctx().begin_pass(raw_input); + self.frame_started = true; + } + + pub fn end_frame_and_draw( + &mut self, + device: &Device, + queue: &Queue, + encoder: &mut CommandEncoder, + window: &Window, + window_surface_view: &TextureView, + screen_descriptor: ScreenDescriptor, + ) { + if !self.frame_started { + panic!("begin_frame must be called before end_frame_and_draw can be called!"); + } + + self.ppp(screen_descriptor.pixels_per_point); + + let full_output = self.state.egui_ctx().end_pass(); + + self.state + .handle_platform_output(window, full_output.platform_output); + + let tris = self + .state + .egui_ctx() + .tessellate(full_output.shapes, self.state.egui_ctx().pixels_per_point()); + for (id, image_delta) in &full_output.textures_delta.set { + self.renderer + .update_texture(device, queue, *id, image_delta); + } + self.renderer + .update_buffers(device, queue, encoder, &tris, &screen_descriptor); + let rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: window_surface_view, + resolve_target: None, + ops: egui_wgpu::wgpu::Operations { + load: egui_wgpu::wgpu::LoadOp::Load, + store: StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + label: Some("egui main render pass"), + occlusion_query_set: None, + }); + + self.renderer + .render(&mut rpass.forget_lifetime(), &tris, &screen_descriptor); + for x in &full_output.textures_delta.free { + self.renderer.free_texture(x) + } + + self.frame_started = false; + } +} diff --git a/src/image.rs b/src/image.rs index 22a7a59..d3bccd9 100644 --- a/src/image.rs +++ b/src/image.rs @@ -1,6 +1,7 @@ use iced::widget::image::Handle; use image::DynamicImage; use image::imageops::FilterType; +use rexiv2::Metadata; use zune_image::codecs::jpeg::JpegDecoder; use zune_image::codecs::qoi::zune_core::colorspace::ColorSpace; use zune_image::codecs::qoi::zune_core::options::DecoderOptions; @@ -17,16 +18,38 @@ pub struct ImflowImageBuffer { pub width: usize, pub height: usize, pub argb_buffer: Vec, + pub rating: i32 } pub fn create_iced_handle(width: u32, height: u32, rgba: Vec) -> Handle { Handle::from_rgba(width, height, rgba) } +fn get_rating(filename: PathBuf) -> i32 { + // if !path_exists(filename.clone()) { + // anyhow::bail!("File doesn't exist"); + // } + + // // Use xmp-toolkit for video files + // if is_video(&filename) { + // return Ok(read_rating_xmp(filename.clone()).unwrap_or(0)); + // } + + // Use rexiv2 for image files + let meta = Metadata::new_from_path(filename); + match meta { + Ok(meta) => { + let rating = meta.get_tag_numeric("Xmp.xmp.Rating"); + rating + } + Err(e) => panic!("{:?}", e), + } +} + pub fn load_image(path: PathBuf) -> ImflowImageBuffer { let total_start = Instant::now(); - let file = read(path).unwrap(); + let file = read(path.clone()).unwrap(); let mut decoder = JpegDecoder::new(&file); let options = DecoderOptions::new_fast().jpeg_set_out_colorspace(ColorSpace::BGRA); decoder.set_options(options); @@ -52,10 +75,13 @@ pub fn load_image(path: PathBuf) -> ImflowImageBuffer { let total_time = total_start.elapsed(); println!("Total loading time: {:?}", total_time); + let rating = get_rating(path); + ImflowImageBuffer { width, height, argb_buffer: buffer_u32, + rating } } @@ -63,12 +89,14 @@ pub fn image_to_argb_buffer(img: DynamicImage) -> Vec { let flat = img.into_rgba8(); let buf = flat.as_raw(); - buf.chunks_exact(4).map(|rgba| { - let r = rgba[0] as u32; - let g = rgba[1] as u32; - let b = rgba[2] as u32; - r << 16 | g << 8 | b - }).collect() + buf.chunks_exact(4) + .map(|rgba| { + let r = rgba[0] as u32; + let g = rgba[1] as u32; + let b = rgba[2] as u32; + r << 16 | g << 8 | b + }) + .collect() } pub fn load_available_images(dir: PathBuf) -> Vec { @@ -121,10 +149,13 @@ pub fn load_thumbnail_exif(path: &PathBuf) -> Option { *argb = r << 16 | g << 8 | b; } + let rating = get_rating(path.into()); + Some(ImflowImageBuffer { width, height, argb_buffer: buffer, + rating }) } _ => None, @@ -143,10 +174,12 @@ pub fn load_thumbnail_full(path: &PathBuf) -> ImflowImageBuffer { let width = image.width() as usize; let height = image.height() as usize; let buffer = image_to_argb_buffer(image); + let rating = get_rating(path.into()); ImflowImageBuffer { width, height, argb_buffer: buffer, + rating } } diff --git a/src/main.rs b/src/main.rs index c0280bc..e885709 100644 --- a/src/main.rs +++ b/src/main.rs @@ -317,6 +317,66 @@ use imflow::store::ImageStore; use eframe::egui; use egui::{ColorImage, Image, TextureHandle, TextureOptions}; +mod app; +mod egui_tools; + +use winit::event_loop::{ControlFlow, EventLoop}; + +fn main() { + #[cfg(not(target_arch = "wasm32"))] + { + pollster::block_on(run()); + } +} + +async fn run() { + let event_loop = EventLoop::new().unwrap(); + + event_loop.set_control_flow(ControlFlow::Poll); + + let mut app = app::App::new(); + + event_loop.run_app(&mut app).expect("Failed to run app"); + + // let path = args.path.unwrap_or("./test_images".into()); + // let mut state = ImageStore::new(path); + // let mut waiting = true; + // window.set_key_repeat_delay(0.1); + // window.set_key_repeat_rate(0.1); + + // show_image(&mut window, state.get_thumbnail()); + + // while window.is_open() && !window.is_key_down(Key::Escape) { + // window.update(); + // state.check_loaded_images(); + // if window.is_key_pressed(Key::Right, minifb::KeyRepeat::Yes) { + // state.next_image(1); + // if let Some(full) = state.get_current_image() { + // show_image(&mut window, full); + // } else { + // show_image(&mut window, state.get_thumbnail()); + // waiting = true; + // } + // } else if window.is_key_pressed(Key::Left, minifb::KeyRepeat::Yes) { + // state.next_image(-1); + // if let Some(full) = state.get_current_image() { + // show_image(&mut window, full); + // } else { + // show_image(&mut window, state.get_thumbnail()); + // waiting = true; + // } + // } + // if waiting { + // if let Some(image) = state.get_current_image() { + // waiting = false; + + // show_image(&mut window, &image); + // } + // } + // } +} + + struct MyApp { // image: Image, store: ImageStore, @@ -361,105 +421,133 @@ struct Args { path: Option, } -fn main() { - let native_options = eframe::NativeOptions { - viewport: egui::ViewportBuilder::default().with_inner_size((400.0, 400.0)), - ..eframe::NativeOptions::default() - }; +// fn init_app() { + +// let mut store = ImageStore::new("./test_images".into()); - eframe::run_native( - "aaa", - native_options, - Box::new(|cc| { - // Initialize image loaders - egui_extras::install_image_loaders(&cc.egui_ctx); - let mut store = ImageStore::new("./test_images".into()); +// let mut imbuf = store.get_current_image().unwrap(); - let mut imbuf = store.get_current_image().unwrap(); +// let width = imbuf.width; +// let height = imbuf.height; - let width = imbuf.width; - let height = imbuf.height; +// let mut buffer = imbuf.argb_buffer.clone(); +// // Reinterpret to avoid copying +// let buffer_u8 = unsafe { +// Vec::from_raw_parts( +// buffer.as_mut_ptr() as *mut u8, +// buffer.len() * 4, +// buffer.capacity() * 4, +// ) +// }; +// std::mem::forget(buffer); - let mut buffer = imbuf.argb_buffer.clone(); - // Reinterpret to avoid copying - let buffer_u8 = unsafe { - Vec::from_raw_parts( - buffer.as_mut_ptr() as *mut u8, - buffer.len() * 4, - buffer.capacity() * 4, - ) - }; - std::mem::forget(buffer); +// let color_image = ColorImage::from_rgba_unmultiplied([width, height], &buffer_u8); +// let texture = cc +// .egui_ctx +// .load_texture("img", color_image, TextureOptions::LINEAR); - let color_image = ColorImage::from_rgba_unmultiplied([width, height], &buffer_u8); - let texture = cc - .egui_ctx - .load_texture("img", color_image, TextureOptions::LINEAR); +// Ok(Box::new(MyApp::new(store, texture))) +// } - Ok(Box::new(MyApp::new(store, texture))) - }), - ) - .unwrap(); - // eframe::run_native(Box::new(MyApp::default()), options); +// fn main() { +// let native_options = eframe::NativeOptions { +// viewport: egui::ViewportBuilder::default().with_inner_size((400.0, 400.0)), +// ..eframe::NativeOptions::default() +// }; - let args = Args::parse(); - const WIDTH: usize = 2000; - const HEIGHT: usize = 1000; - let mut window = Window::new( - "Test - ESC to exit", - WIDTH, - HEIGHT, - WindowOptions::default(), - ) - .unwrap_or_else(|e| { - panic!("{}", e); - }); +// eframe::run_native( +// "aaa", +// native_options, +// Box::new(|cc| { +// // Initialize image loaders +// egui_extras::install_image_loaders(&cc.egui_ctx); +// let mut store = ImageStore::new("./test_images".into()); - window.set_target_fps(120); +// let mut imbuf = store.get_current_image().unwrap(); - let path = args.path.unwrap_or("./test_images".into()); - let mut state = ImageStore::new(path); - let mut waiting = true; - window.set_key_repeat_delay(0.1); - window.set_key_repeat_rate(0.1); +// let width = imbuf.width; +// let height = imbuf.height; - show_image(&mut window, state.get_thumbnail()); +// let mut buffer = imbuf.argb_buffer.clone(); +// // Reinterpret to avoid copying +// let buffer_u8 = unsafe { +// Vec::from_raw_parts( +// buffer.as_mut_ptr() as *mut u8, +// buffer.len() * 4, +// buffer.capacity() * 4, +// ) +// }; +// std::mem::forget(buffer); - while window.is_open() && !window.is_key_down(Key::Escape) { - window.update(); - state.check_loaded_images(); - if window.is_key_pressed(Key::Right, minifb::KeyRepeat::Yes) { - state.next_image(1); - if let Some(full) = state.get_current_image() { - show_image(&mut window, full); - } else { - show_image(&mut window, state.get_thumbnail()); - waiting = true; - } - } else if window.is_key_pressed(Key::Left, minifb::KeyRepeat::Yes) { - state.next_image(-1); - if let Some(full) = state.get_current_image() { - show_image(&mut window, full); - } else { - show_image(&mut window, state.get_thumbnail()); - waiting = true; - } - } - if waiting { - if let Some(image) = state.get_current_image() { - waiting = false; +// let color_image = ColorImage::from_rgba_unmultiplied([width, height], &buffer_u8); +// let texture = cc +// .egui_ctx +// .load_texture("img", color_image, TextureOptions::LINEAR); - show_image(&mut window, &image); - } - } - } -} +// Ok(Box::new(MyApp::new(store, texture))) +// }), +// ) +// .unwrap(); +// // eframe::run_native(Box::new(MyApp::default()), options); -fn show_image(window: &mut Window, image: &ImflowImageBuffer) { - window - .update_with_buffer(&image.argb_buffer, image.width, image.height) - .unwrap(); -} +// let args = Args::parse(); +// const WIDTH: usize = 2000; +// const HEIGHT: usize = 1000; +// let mut window = Window::new( +// "Test - ESC to exit", +// WIDTH, +// HEIGHT, +// WindowOptions::default(), +// ) +// .unwrap_or_else(|e| { +// panic!("{}", e); +// }); + +// window.set_target_fps(120); + +// let path = args.path.unwrap_or("./test_images".into()); +// let mut state = ImageStore::new(path); +// let mut waiting = true; +// window.set_key_repeat_delay(0.1); +// window.set_key_repeat_rate(0.1); + +// show_image(&mut window, state.get_thumbnail()); + +// while window.is_open() && !window.is_key_down(Key::Escape) { +// window.update(); +// state.check_loaded_images(); +// if window.is_key_pressed(Key::Right, minifb::KeyRepeat::Yes) { +// state.next_image(1); +// if let Some(full) = state.get_current_image() { +// show_image(&mut window, full); +// } else { +// show_image(&mut window, state.get_thumbnail()); +// waiting = true; +// } +// } else if window.is_key_pressed(Key::Left, minifb::KeyRepeat::Yes) { +// state.next_image(-1); +// if let Some(full) = state.get_current_image() { +// show_image(&mut window, full); +// } else { +// show_image(&mut window, state.get_thumbnail()); +// waiting = true; +// } +// } +// if waiting { +// if let Some(image) = state.get_current_image() { +// waiting = false; + +// show_image(&mut window, &image); +// } +// } +// } +// } + +// fn show_image(window: &mut Window, image: &ImflowImageBuffer) { +// window +// .update_with_buffer(&image.argb_buffer, image.width, image.height) +// .unwrap(); +// } // struct MainApp { // is_playing: bool, diff --git a/src/shader.wgsl b/src/shader.wgsl new file mode 100644 index 0000000..fb80642 --- /dev/null +++ b/src/shader.wgsl @@ -0,0 +1,25 @@ +struct VertexInput { + @location(0) position: vec3, + @location(1) uv: vec2, +}; + +struct VertexOutput { + @builtin(position) position: vec4, + @location(0) uv: vec2, +}; + +@vertex +fn vs_main(in: VertexInput) -> VertexOutput { + var out: VertexOutput; + out.position = vec4(in.position, 1.0); + out.uv = in.uv; + return out; +} + +@group(0) @binding(0) var texture: texture_2d; +@group(0) @binding(1) var texture_sampler: sampler; + +@fragment +fn fs_main(@location(0) uv: vec2) -> @location(0) vec4 { + return textureSample(texture, texture_sampler, uv); +} diff --git a/src/store.rs b/src/store.rs index 3c7156e..15c691e 100644 --- a/src/store.rs +++ b/src/store.rs @@ -1,7 +1,6 @@ use crate::image::load_thumbnail; -use crate::image::{ - ImflowImageBuffer, load_available_images, load_image, -}; +use crate::image::{ImflowImageBuffer, load_available_images, load_image}; +use rexiv2::Metadata; use std::collections::HashMap; use std::collections::HashSet; use std::path::PathBuf; @@ -73,6 +72,36 @@ impl ImageStore { state } + pub fn set_rating(&mut self, rating: i32) { + let meta = Metadata::new_from_path(self.current_image_path.clone()); + match meta { + Ok(meta) => { + meta.set_tag_numeric("Xmp.xmp.Rating", rating).unwrap(); + meta.save_to_file(self.current_image_path.clone()).unwrap(); + } + Err(e) => panic!("{:?}", e), + } + if let Some(full) = self.loaded_images.get_mut(&self.current_image_path.clone()) { + full.rating = rating; + } + if let Some(thumbnail) = self.loaded_images_thumbnails.get_mut(&self.current_image_path.clone()) { + thumbnail.rating = rating; + } + } + + pub fn get_current_rating(&self) -> i32 { + let imbuf = if let Some(full) = self.get_current_image() { + println!("full"); + full + } else { + // TODO: this assumes loaded thumbnail + self.loaded_images_thumbnails + .get(&self.current_image_path) + .unwrap() + }; + imbuf.rating + } + pub fn preload_next_images(&mut self, n: usize) { for image in self .available_images