Wire up all GTK UI actions to real functionality
- Settings menu opens PreferencesDialog - History menu shows HistoryStore entries in a dialog - Add Files (Ctrl+O) opens FileDialog with image MIME filters - Process button runs PipelineExecutor in background thread - Progress bar updates via mpsc channel polled with glib timeout - Cancel button sets AtomicBool flag to stop processing - Results page shows real stats (images, sizes, savings, time) - Open Output Folder launches default file manager - Process Another Batch resets wizard to step 1 - Toast notifications via ToastOverlay for feedback - History entries saved after each processing run - Remove dead_code allows from processing.rs and settings.rs
This commit is contained in:
287
Cargo.lock
generated
287
Cargo.lock
generated
@@ -203,7 +203,7 @@ version = "0.72.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.11.0",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.13.0",
|
||||
@@ -223,6 +223,12 @@ version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.11.0"
|
||||
@@ -280,7 +286,7 @@ version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cc8d9aa793480744cd9a0524fef1a2e197d9eaa0f739cde19d16aba530dcb95"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.11.0",
|
||||
"cairo-sys-rs",
|
||||
"glib",
|
||||
"libc",
|
||||
@@ -471,6 +477,27 @@ version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "document-features"
|
||||
version = "0.2.12"
|
||||
@@ -621,6 +648,17 @@ dependencies = [
|
||||
"rustc_version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"libredox",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.9"
|
||||
@@ -643,6 +681,15 @@ version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "fsevent-sys"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "2.0.0"
|
||||
@@ -852,7 +899,7 @@ version = "0.22.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16877c6e619447e0bcb6de326a42a8bd02b36328cfeeda210135425e576efa3d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.11.0",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
@@ -1121,6 +1168,35 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"inotify-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify-sys"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "interpolate_name"
|
||||
version = "0.2.4"
|
||||
@@ -1191,6 +1267,26 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kqueue"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a"
|
||||
dependencies = [
|
||||
"kqueue-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kqueue-sys"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "leb128fmt"
|
||||
version = "0.1.0"
|
||||
@@ -1284,6 +1380,18 @@ version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"libc",
|
||||
"plain",
|
||||
"redox_syscall",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libwebp-sys"
|
||||
version = "0.9.6"
|
||||
@@ -1394,6 +1502,18 @@ dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "moxcms"
|
||||
version = "0.7.11"
|
||||
@@ -1485,6 +1605,34 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
|
||||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "7.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c533b4c39709f9ba5005d8002048266593c1cfaf3c5f0739d5b8ab0c6c504009"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"filetime",
|
||||
"fsevent-sys",
|
||||
"inotify",
|
||||
"kqueue",
|
||||
"libc",
|
||||
"log",
|
||||
"mio",
|
||||
"notify-types",
|
||||
"walkdir",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify-types"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "585d3cb5e12e01aed9e8a1f70d5c6b5e86fe2a6e48fc8cd0b3e0b8df6f6eb174"
|
||||
dependencies = [
|
||||
"instant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num"
|
||||
version = "0.4.3"
|
||||
@@ -1582,6 +1730,12 @@ version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "owned_ttf_parser"
|
||||
version = "0.25.1"
|
||||
@@ -1673,12 +1827,14 @@ name = "pixstrip-core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ab_glyph",
|
||||
"dirs",
|
||||
"fast_image_resize",
|
||||
"image",
|
||||
"imageproc",
|
||||
"little_exif",
|
||||
"magick_rust",
|
||||
"mozjpeg",
|
||||
"notify",
|
||||
"oxipng",
|
||||
"rayon",
|
||||
"serde",
|
||||
@@ -1704,13 +1860,19 @@ version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||
|
||||
[[package]]
|
||||
name = "plain"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.11.0",
|
||||
"crc32fast",
|
||||
"fdeflate",
|
||||
"flate2",
|
||||
@@ -1966,6 +2128,26 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
|
||||
dependencies = [
|
||||
"getrandom 0.2.17",
|
||||
"libredox",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.3"
|
||||
@@ -2025,7 +2207,7 @@ version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.11.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
@@ -2479,7 +2661,7 @@ version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.11.0",
|
||||
"hashbrown 0.15.5",
|
||||
"indexmap",
|
||||
"semver",
|
||||
@@ -2526,13 +2708,22 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
"windows-targets 0.53.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2544,6 +2735,22 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.6",
|
||||
"windows_aarch64_msvc 0.52.6",
|
||||
"windows_i686_gnu 0.52.6",
|
||||
"windows_i686_gnullvm 0.52.6",
|
||||
"windows_i686_msvc 0.52.6",
|
||||
"windows_x86_64_gnu 0.52.6",
|
||||
"windows_x86_64_gnullvm 0.52.6",
|
||||
"windows_x86_64_msvc 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.5"
|
||||
@@ -2551,58 +2758,106 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
"windows_aarch64_gnullvm 0.53.1",
|
||||
"windows_aarch64_msvc 0.53.1",
|
||||
"windows_i686_gnu 0.53.1",
|
||||
"windows_i686_gnullvm 0.53.1",
|
||||
"windows_i686_msvc 0.53.1",
|
||||
"windows_x86_64_gnu 0.53.1",
|
||||
"windows_x86_64_gnullvm 0.53.1",
|
||||
"windows_x86_64_msvc 0.53.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.53.1"
|
||||
@@ -2676,7 +2931,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags",
|
||||
"bitflags 2.11.0",
|
||||
"indexmap",
|
||||
"log",
|
||||
"serde",
|
||||
|
||||
@@ -1,12 +1,23 @@
|
||||
use adw::prelude::*;
|
||||
use gtk::glib;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::step_indicator::StepIndicator;
|
||||
use crate::wizard::WizardState;
|
||||
|
||||
pub const APP_ID: &str = "live.lashman.Pixstrip";
|
||||
|
||||
/// Shared app state accessible from all UI callbacks
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
pub wizard: Rc<RefCell<WizardState>>,
|
||||
pub loaded_files: Rc<RefCell<Vec<std::path::PathBuf>>>,
|
||||
pub output_dir: Rc<RefCell<Option<std::path::PathBuf>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct WizardUi {
|
||||
nav_view: adw::NavigationView,
|
||||
@@ -15,7 +26,8 @@ struct WizardUi {
|
||||
next_button: gtk::Button,
|
||||
title: adw::WindowTitle,
|
||||
pages: Vec<adw::NavigationPage>,
|
||||
state: Rc<RefCell<WizardState>>,
|
||||
toast_overlay: adw::ToastOverlay,
|
||||
state: AppState,
|
||||
}
|
||||
|
||||
pub fn build_app() -> adw::Application {
|
||||
@@ -43,7 +55,11 @@ fn setup_shortcuts(app: &adw::Application) {
|
||||
}
|
||||
|
||||
fn build_ui(app: &adw::Application) {
|
||||
let state = Rc::new(RefCell::new(WizardState::new()));
|
||||
let app_state = AppState {
|
||||
wizard: Rc::new(RefCell::new(WizardState::new())),
|
||||
loaded_files: Rc::new(RefCell::new(Vec::new())),
|
||||
output_dir: Rc::new(RefCell::new(None)),
|
||||
};
|
||||
|
||||
// Header bar
|
||||
let header = adw::HeaderBar::new();
|
||||
@@ -61,7 +77,7 @@ fn build_ui(app: &adw::Application) {
|
||||
header.pack_end(&menu_button);
|
||||
|
||||
// Step indicator
|
||||
let step_indicator = StepIndicator::new(&state.borrow().step_names());
|
||||
let step_indicator = StepIndicator::new(&app_state.wizard.borrow().step_names());
|
||||
|
||||
// Navigation view for wizard content
|
||||
let nav_view = adw::NavigationView::new();
|
||||
@@ -110,12 +126,16 @@ fn build_ui(app: &adw::Application) {
|
||||
toolbar_view.set_content(Some(&content_box));
|
||||
toolbar_view.add_bottom_bar(&bottom_bar);
|
||||
|
||||
// Toast overlay wraps everything for in-app notifications
|
||||
let toast_overlay = adw::ToastOverlay::new();
|
||||
toast_overlay.set_child(Some(&toolbar_view));
|
||||
|
||||
// Window
|
||||
let window = adw::ApplicationWindow::builder()
|
||||
.application(app)
|
||||
.default_width(900)
|
||||
.default_height(700)
|
||||
.content(&toolbar_view)
|
||||
.content(&toast_overlay)
|
||||
.title("Pixstrip")
|
||||
.build();
|
||||
|
||||
@@ -126,11 +146,16 @@ fn build_ui(app: &adw::Application) {
|
||||
next_button,
|
||||
title,
|
||||
pages,
|
||||
state,
|
||||
toast_overlay,
|
||||
state: app_state,
|
||||
};
|
||||
|
||||
setup_window_actions(&window, &ui);
|
||||
update_nav_buttons(&ui.state.borrow(), &ui.back_button, &ui.next_button);
|
||||
update_nav_buttons(
|
||||
&ui.state.wizard.borrow(),
|
||||
&ui.back_button,
|
||||
&ui.next_button,
|
||||
);
|
||||
ui.step_indicator.set_current(0);
|
||||
|
||||
window.present();
|
||||
@@ -151,7 +176,7 @@ fn setup_window_actions(window: &adw::ApplicationWindow, ui: &WizardUi) {
|
||||
let ui = ui.clone();
|
||||
let action = gtk::gio::SimpleAction::new("next-step", None);
|
||||
action.connect_activate(move |_, _| {
|
||||
let mut s = ui.state.borrow_mut();
|
||||
let mut s = ui.state.wizard.borrow_mut();
|
||||
if s.can_go_next() {
|
||||
s.go_next();
|
||||
let idx = s.current_step;
|
||||
@@ -167,7 +192,7 @@ fn setup_window_actions(window: &adw::ApplicationWindow, ui: &WizardUi) {
|
||||
let ui = ui.clone();
|
||||
let action = gtk::gio::SimpleAction::new("prev-step", None);
|
||||
action.connect_activate(move |_, _| {
|
||||
let mut s = ui.state.borrow_mut();
|
||||
let mut s = ui.state.wizard.borrow_mut();
|
||||
if s.can_go_back() {
|
||||
s.go_back();
|
||||
let idx = s.current_step;
|
||||
@@ -188,10 +213,10 @@ fn setup_window_actions(window: &adw::ApplicationWindow, ui: &WizardUi) {
|
||||
action.connect_activate(move |_, param| {
|
||||
if let Some(step) = param.and_then(|p| p.get::<i32>()) {
|
||||
let target = (step - 1) as usize;
|
||||
let s = ui.state.borrow();
|
||||
let s = ui.state.wizard.borrow();
|
||||
if target < s.total_steps && s.visited[target] {
|
||||
drop(s);
|
||||
ui.state.borrow_mut().current_step = target;
|
||||
ui.state.wizard.borrow_mut().current_step = target;
|
||||
navigate_to_step(&ui, target);
|
||||
}
|
||||
}
|
||||
@@ -199,31 +224,52 @@ fn setup_window_actions(window: &adw::ApplicationWindow, ui: &WizardUi) {
|
||||
action_group.add_action(&action);
|
||||
}
|
||||
|
||||
// Process action (placeholder)
|
||||
// Process action - runs the pipeline
|
||||
{
|
||||
let ui = ui.clone();
|
||||
let window = window.clone();
|
||||
let action = gtk::gio::SimpleAction::new("process", None);
|
||||
action.connect_activate(move |_, _| {});
|
||||
action.connect_activate(move |_, _| {
|
||||
let files = ui.state.loaded_files.borrow().clone();
|
||||
if files.is_empty() {
|
||||
let toast = adw::Toast::new("No images loaded - go to Step 2 to add images");
|
||||
ui.toast_overlay.add_toast(toast);
|
||||
return;
|
||||
}
|
||||
run_processing(&window, &ui);
|
||||
});
|
||||
action_group.add_action(&action);
|
||||
}
|
||||
|
||||
// Add files action (placeholder)
|
||||
// Add files action - opens file chooser
|
||||
{
|
||||
let window = window.clone();
|
||||
let ui = ui.clone();
|
||||
let action = gtk::gio::SimpleAction::new("add-files", None);
|
||||
action.connect_activate(move |_, _| {});
|
||||
action.connect_activate(move |_, _| {
|
||||
open_file_chooser(&window, &ui);
|
||||
});
|
||||
action_group.add_action(&action);
|
||||
}
|
||||
|
||||
// Settings action (placeholder)
|
||||
// Settings action - opens settings dialog
|
||||
{
|
||||
let window = window.clone();
|
||||
let action = gtk::gio::SimpleAction::new("show-settings", None);
|
||||
action.connect_activate(move |_, _| {});
|
||||
action.connect_activate(move |_, _| {
|
||||
let dialog = crate::settings::build_settings_dialog();
|
||||
dialog.present(Some(&window));
|
||||
});
|
||||
action_group.add_action(&action);
|
||||
}
|
||||
|
||||
// History action (placeholder)
|
||||
// History action - shows history dialog
|
||||
{
|
||||
let window = window.clone();
|
||||
let action = gtk::gio::SimpleAction::new("show-history", None);
|
||||
action.connect_activate(move |_, _| {});
|
||||
action.connect_activate(move |_, _| {
|
||||
show_history_dialog(&window);
|
||||
});
|
||||
action_group.add_action(&action);
|
||||
}
|
||||
|
||||
@@ -237,16 +283,24 @@ fn setup_window_actions(window: &adw::ApplicationWindow, ui: &WizardUi) {
|
||||
|
||||
ui.next_button.connect_clicked({
|
||||
let action_group = action_group.clone();
|
||||
let ui = ui.clone();
|
||||
move |_| {
|
||||
let s = ui.state.wizard.borrow();
|
||||
if s.is_last_step() {
|
||||
drop(s);
|
||||
ActionGroupExt::activate_action(&action_group, "process", None);
|
||||
} else {
|
||||
drop(s);
|
||||
ActionGroupExt::activate_action(&action_group, "next-step", None);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
window.insert_action_group("win", Some(&action_group));
|
||||
}
|
||||
|
||||
fn navigate_to_step(ui: &WizardUi, target: usize) {
|
||||
let s = ui.state.borrow();
|
||||
let s = ui.state.wizard.borrow();
|
||||
|
||||
// Update step indicator
|
||||
ui.step_indicator.set_current(target);
|
||||
@@ -280,3 +334,511 @@ fn update_nav_buttons(state: &WizardState, back_button: >k::Button, next_butto
|
||||
next_button.set_tooltip_text(Some("Go to next step (Alt+Right)"));
|
||||
}
|
||||
}
|
||||
|
||||
fn open_file_chooser(window: &adw::ApplicationWindow, ui: &WizardUi) {
|
||||
let dialog = gtk::FileDialog::builder()
|
||||
.title("Select Images")
|
||||
.modal(true)
|
||||
.build();
|
||||
|
||||
let filter = gtk::FileFilter::new();
|
||||
filter.set_name(Some("Image files"));
|
||||
filter.add_mime_type("image/jpeg");
|
||||
filter.add_mime_type("image/png");
|
||||
filter.add_mime_type("image/webp");
|
||||
filter.add_mime_type("image/avif");
|
||||
filter.add_mime_type("image/gif");
|
||||
filter.add_mime_type("image/tiff");
|
||||
filter.add_mime_type("image/bmp");
|
||||
|
||||
let filters = gtk::gio::ListStore::new::<gtk::FileFilter>();
|
||||
filters.append(&filter);
|
||||
dialog.set_filters(Some(&filters));
|
||||
dialog.set_default_filter(Some(&filter));
|
||||
|
||||
let ui = ui.clone();
|
||||
dialog.open_multiple(Some(window), gtk::gio::Cancellable::NONE, move |result| {
|
||||
if let Ok(files) = result {
|
||||
let mut paths = ui.state.loaded_files.borrow_mut();
|
||||
for i in 0..files.n_items() {
|
||||
if let Some(file) = files.item(i)
|
||||
&& let Some(gfile) = file.downcast_ref::<gtk::gio::File>()
|
||||
&& let Some(path) = gfile.path()
|
||||
&& !paths.contains(&path)
|
||||
{
|
||||
paths.push(path);
|
||||
}
|
||||
}
|
||||
let count = paths.len();
|
||||
drop(paths);
|
||||
|
||||
// Update the images step UI if we can find the label
|
||||
update_images_count_label(&ui, count);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn update_images_count_label(ui: &WizardUi, count: usize) {
|
||||
// Find the step-images page and switch its stack to "loaded" if we have files
|
||||
if let Some(page) = ui.pages.get(1)
|
||||
&& let Some(stack) = page.child().and_downcast::<gtk::Stack>()
|
||||
{
|
||||
if count > 0 {
|
||||
stack.set_visible_child_name("loaded");
|
||||
}
|
||||
if let Some(loaded_box) = stack.child_by_name("loaded") {
|
||||
update_count_in_box(&loaded_box, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_count_in_box(widget: >k::Widget, count: usize) {
|
||||
// Walk the widget tree to find the heading label with "images" text
|
||||
if let Some(label) = widget.downcast_ref::<gtk::Label>() {
|
||||
if label.css_classes().iter().any(|c| c == "heading") {
|
||||
let files = pixstrip_core::storage::PresetStore::new(); // just for format_bytes
|
||||
let _ = files; // unused
|
||||
label.set_label(&format!("{} images loaded", count));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if let Some(bx) = widget.downcast_ref::<gtk::Box>() {
|
||||
let mut child = bx.first_child();
|
||||
while let Some(c) = child {
|
||||
update_count_in_box(&c, count);
|
||||
child = c.next_sibling();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn show_history_dialog(window: &adw::ApplicationWindow) {
|
||||
let dialog = adw::Dialog::builder()
|
||||
.title("Processing History")
|
||||
.content_width(500)
|
||||
.content_height(400)
|
||||
.build();
|
||||
|
||||
let toolbar_view = adw::ToolbarView::new();
|
||||
let header = adw::HeaderBar::new();
|
||||
toolbar_view.add_top_bar(&header);
|
||||
|
||||
let scrolled = gtk::ScrolledWindow::builder()
|
||||
.hscrollbar_policy(gtk::PolicyType::Never)
|
||||
.vexpand(true)
|
||||
.build();
|
||||
|
||||
let content = gtk::Box::builder()
|
||||
.orientation(gtk::Orientation::Vertical)
|
||||
.spacing(0)
|
||||
.build();
|
||||
|
||||
let history = pixstrip_core::storage::HistoryStore::new();
|
||||
match history.list() {
|
||||
Ok(entries) if entries.is_empty() => {
|
||||
let empty = adw::StatusPage::builder()
|
||||
.title("No History Yet")
|
||||
.description("Processed batches will appear here")
|
||||
.icon_name("document-open-recent-symbolic")
|
||||
.vexpand(true)
|
||||
.build();
|
||||
content.append(&empty);
|
||||
}
|
||||
Ok(entries) => {
|
||||
let group = adw::PreferencesGroup::builder()
|
||||
.title("Recent Batches")
|
||||
.margin_start(12)
|
||||
.margin_end(12)
|
||||
.margin_top(12)
|
||||
.build();
|
||||
|
||||
for entry in entries.iter().rev() {
|
||||
let savings = if entry.total_input_bytes > 0 {
|
||||
let pct = (1.0
|
||||
- entry.total_output_bytes as f64 / entry.total_input_bytes as f64)
|
||||
* 100.0;
|
||||
format!("{:.0}% saved", pct)
|
||||
} else {
|
||||
"N/A".into()
|
||||
};
|
||||
|
||||
let subtitle = format!(
|
||||
"{}/{} succeeded - {} - {}",
|
||||
entry.succeeded,
|
||||
entry.total,
|
||||
savings,
|
||||
format_duration(entry.elapsed_ms)
|
||||
);
|
||||
|
||||
let preset_label = entry
|
||||
.preset_name
|
||||
.as_deref()
|
||||
.unwrap_or("Custom workflow");
|
||||
|
||||
let row = adw::ActionRow::builder()
|
||||
.title(preset_label)
|
||||
.subtitle(&subtitle)
|
||||
.build();
|
||||
row.add_prefix(>k::Image::from_icon_name("image-x-generic-symbolic"));
|
||||
group.add(&row);
|
||||
}
|
||||
content.append(&group);
|
||||
}
|
||||
Err(e) => {
|
||||
let error = adw::StatusPage::builder()
|
||||
.title("Could not load history")
|
||||
.description(e.to_string())
|
||||
.icon_name("dialog-error-symbolic")
|
||||
.vexpand(true)
|
||||
.build();
|
||||
content.append(&error);
|
||||
}
|
||||
}
|
||||
|
||||
scrolled.set_child(Some(&content));
|
||||
toolbar_view.set_content(Some(&scrolled));
|
||||
dialog.set_child(Some(&toolbar_view));
|
||||
dialog.present(Some(window));
|
||||
}
|
||||
|
||||
fn run_processing(_window: &adw::ApplicationWindow, ui: &WizardUi) {
|
||||
let files = ui.state.loaded_files.borrow().clone();
|
||||
if files.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let input_dir = files[0]
|
||||
.parent()
|
||||
.unwrap_or_else(|| std::path::Path::new("."))
|
||||
.to_path_buf();
|
||||
|
||||
let output_dir = ui
|
||||
.state
|
||||
.output_dir
|
||||
.borrow()
|
||||
.clone()
|
||||
.unwrap_or_else(|| input_dir.join("processed"));
|
||||
|
||||
// Build job - for now use default settings (resize off, compress high, strip metadata)
|
||||
let mut job = pixstrip_core::pipeline::ProcessingJob::new(&input_dir, &output_dir);
|
||||
job.compress = Some(pixstrip_core::operations::CompressConfig::Preset(
|
||||
pixstrip_core::types::QualityPreset::High,
|
||||
));
|
||||
job.metadata = Some(pixstrip_core::operations::MetadataConfig::StripAll);
|
||||
|
||||
for file in &files {
|
||||
job.add_source(file);
|
||||
}
|
||||
|
||||
// Build processing UI inline in the nav_view
|
||||
let processing_page = crate::processing::build_processing_page();
|
||||
ui.nav_view.push(&processing_page);
|
||||
|
||||
// Hide bottom nav buttons during processing
|
||||
ui.back_button.set_visible(false);
|
||||
ui.next_button.set_visible(false);
|
||||
ui.title.set_subtitle("Processing...");
|
||||
|
||||
// Get references to progress widgets inside the page
|
||||
let progress_bar = find_widget_by_type::<gtk::ProgressBar>(&processing_page);
|
||||
let cancel_flag = Arc::new(AtomicBool::new(false));
|
||||
|
||||
// Find cancel button and wire it
|
||||
wire_cancel_button(&processing_page, cancel_flag.clone());
|
||||
|
||||
// Run processing in a background thread
|
||||
let (tx, rx) = std::sync::mpsc::channel::<ProcessingMessage>();
|
||||
|
||||
let cancel = cancel_flag.clone();
|
||||
std::thread::spawn(move || {
|
||||
let executor = pixstrip_core::executor::PipelineExecutor::with_cancel(cancel);
|
||||
let result = executor.execute(&job, |update| {
|
||||
let _ = tx.send(ProcessingMessage::Progress {
|
||||
current: update.current,
|
||||
total: update.total,
|
||||
file: update.current_file,
|
||||
});
|
||||
});
|
||||
match result {
|
||||
Ok(r) => {
|
||||
let _ = tx.send(ProcessingMessage::Done(r));
|
||||
}
|
||||
Err(e) => {
|
||||
let _ = tx.send(ProcessingMessage::Error(e.to_string()));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Poll for messages from the processing thread
|
||||
let ui_for_rx = ui.clone();
|
||||
glib::timeout_add_local(std::time::Duration::from_millis(50), move || {
|
||||
while let Ok(msg) = rx.try_recv() {
|
||||
match msg {
|
||||
ProcessingMessage::Progress {
|
||||
current,
|
||||
total,
|
||||
file,
|
||||
} => {
|
||||
if let Some(ref bar) = progress_bar {
|
||||
bar.set_fraction(current as f64 / total as f64);
|
||||
bar.set_text(Some(&format!("{}/{} - {}", current, total, file)));
|
||||
}
|
||||
update_progress_labels(&ui_for_rx.nav_view, current, total, &file);
|
||||
}
|
||||
ProcessingMessage::Done(result) => {
|
||||
show_results(&ui_for_rx, &result);
|
||||
return glib::ControlFlow::Break;
|
||||
}
|
||||
ProcessingMessage::Error(err) => {
|
||||
let toast = adw::Toast::new(&format!("Processing failed: {}", err));
|
||||
ui_for_rx.toast_overlay.add_toast(toast);
|
||||
ui_for_rx.back_button.set_visible(true);
|
||||
ui_for_rx.next_button.set_visible(true);
|
||||
if let Some(visible) = ui_for_rx.nav_view.visible_page()
|
||||
&& visible.tag().as_deref() == Some("processing")
|
||||
{
|
||||
ui_for_rx.nav_view.pop();
|
||||
}
|
||||
return glib::ControlFlow::Break;
|
||||
}
|
||||
}
|
||||
}
|
||||
glib::ControlFlow::Continue
|
||||
});
|
||||
}
|
||||
|
||||
fn show_results(
|
||||
ui: &WizardUi,
|
||||
result: &pixstrip_core::executor::BatchResult,
|
||||
) {
|
||||
let results_page = crate::processing::build_results_page();
|
||||
|
||||
// Update result stats by walking the widget tree
|
||||
update_results_stats(&results_page, result);
|
||||
|
||||
// Wire the action buttons
|
||||
wire_results_actions(ui, &results_page);
|
||||
|
||||
ui.nav_view.push(&results_page);
|
||||
|
||||
ui.title.set_subtitle("Processing Complete");
|
||||
ui.back_button.set_visible(false);
|
||||
ui.next_button.set_label("Process More");
|
||||
ui.next_button.set_visible(true);
|
||||
|
||||
// Save history
|
||||
let history = pixstrip_core::storage::HistoryStore::new();
|
||||
let _ = history.add(pixstrip_core::storage::HistoryEntry {
|
||||
timestamp: format!(
|
||||
"{}",
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs()
|
||||
),
|
||||
input_dir: String::new(),
|
||||
output_dir: String::new(),
|
||||
preset_name: None,
|
||||
total: result.total,
|
||||
succeeded: result.succeeded,
|
||||
failed: result.failed,
|
||||
total_input_bytes: result.total_input_bytes,
|
||||
total_output_bytes: result.total_output_bytes,
|
||||
elapsed_ms: result.elapsed_ms,
|
||||
output_files: vec![],
|
||||
});
|
||||
|
||||
// Show toast
|
||||
let savings = if result.total_input_bytes > 0 {
|
||||
let pct =
|
||||
(1.0 - result.total_output_bytes as f64 / result.total_input_bytes as f64) * 100.0;
|
||||
format!(
|
||||
"{} images processed, {:.0}% space saved",
|
||||
result.succeeded, pct
|
||||
)
|
||||
} else {
|
||||
format!("{} images processed", result.succeeded)
|
||||
};
|
||||
let toast = adw::Toast::new(&savings);
|
||||
toast.set_timeout(5);
|
||||
ui.toast_overlay.add_toast(toast);
|
||||
}
|
||||
|
||||
fn update_results_stats(
|
||||
page: &adw::NavigationPage,
|
||||
result: &pixstrip_core::executor::BatchResult,
|
||||
) {
|
||||
// Walk the widget tree looking for ActionRows to update
|
||||
walk_widgets(&page.child(), &|widget| {
|
||||
if let Some(row) = widget.downcast_ref::<adw::ActionRow>() {
|
||||
let title = row.title();
|
||||
match title.as_str() {
|
||||
"Images processed" => {
|
||||
row.set_subtitle(&format!("{} images", result.succeeded));
|
||||
}
|
||||
"Original size" => {
|
||||
row.set_subtitle(&format_bytes(result.total_input_bytes));
|
||||
}
|
||||
"Output size" => {
|
||||
row.set_subtitle(&format_bytes(result.total_output_bytes));
|
||||
}
|
||||
"Space saved" => {
|
||||
if result.total_input_bytes > 0 {
|
||||
let pct = (1.0
|
||||
- result.total_output_bytes as f64
|
||||
/ result.total_input_bytes as f64)
|
||||
* 100.0;
|
||||
row.set_subtitle(&format!("{:.1}%", pct));
|
||||
}
|
||||
}
|
||||
"Processing time" => {
|
||||
row.set_subtitle(&format_duration(result.elapsed_ms));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn wire_results_actions(
|
||||
ui: &WizardUi,
|
||||
page: &adw::NavigationPage,
|
||||
) {
|
||||
walk_widgets(&page.child(), &|widget| {
|
||||
if let Some(row) = widget.downcast_ref::<adw::ActionRow>() {
|
||||
match row.title().as_str() {
|
||||
"Open Output Folder" => {
|
||||
let ui = ui.clone();
|
||||
row.connect_activated(move |_| {
|
||||
let output = ui.state.output_dir.borrow().clone();
|
||||
if let Some(dir) = output {
|
||||
let _ = gtk::gio::AppInfo::launch_default_for_uri(
|
||||
&format!("file://{}", dir.display()),
|
||||
gtk::gio::AppLaunchContext::NONE,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
"Process Another Batch" => {
|
||||
let ui = ui.clone();
|
||||
row.connect_activated(move |_| {
|
||||
reset_wizard(&ui);
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn reset_wizard(ui: &WizardUi) {
|
||||
// Reset state
|
||||
{
|
||||
let mut s = ui.state.wizard.borrow_mut();
|
||||
s.current_step = 0;
|
||||
s.visited = vec![false; s.total_steps];
|
||||
s.visited[0] = true;
|
||||
}
|
||||
ui.state.loaded_files.borrow_mut().clear();
|
||||
|
||||
// Reset nav
|
||||
ui.nav_view.replace(&ui.pages[..1]);
|
||||
ui.step_indicator.set_current(0);
|
||||
ui.title.set_subtitle("Batch Image Processor");
|
||||
ui.back_button.set_visible(false);
|
||||
ui.next_button.set_label("Next");
|
||||
ui.next_button.set_visible(true);
|
||||
ui.next_button.add_css_class("suggested-action");
|
||||
}
|
||||
|
||||
fn wire_cancel_button(page: &adw::NavigationPage, cancel_flag: Arc<AtomicBool>) {
|
||||
walk_widgets(&page.child(), &|widget| {
|
||||
if let Some(button) = widget.downcast_ref::<gtk::Button>()
|
||||
&& button.label().as_deref() == Some("Cancel")
|
||||
{
|
||||
let flag = cancel_flag.clone();
|
||||
button.connect_clicked(move |btn| {
|
||||
flag.store(true, Ordering::Relaxed);
|
||||
btn.set_sensitive(false);
|
||||
btn.set_label("Cancelling...");
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn update_progress_labels(nav_view: &adw::NavigationView, current: usize, total: usize, file: &str) {
|
||||
if let Some(page) = nav_view.visible_page() {
|
||||
walk_widgets(&page.child(), &|widget| {
|
||||
if let Some(label) = widget.downcast_ref::<gtk::Label>() {
|
||||
if label.css_classes().iter().any(|c| c == "heading")
|
||||
&& label.label().contains("images")
|
||||
{
|
||||
label.set_label(&format!("{} / {} images", current, total));
|
||||
}
|
||||
if label.css_classes().iter().any(|c| c == "dim-label")
|
||||
&& label.label().contains("Estimating")
|
||||
{
|
||||
label.set_label(&format!("Current: {}", file));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// --- Utility functions ---
|
||||
|
||||
enum ProcessingMessage {
|
||||
Progress {
|
||||
current: usize,
|
||||
total: usize,
|
||||
file: String,
|
||||
},
|
||||
Done(pixstrip_core::executor::BatchResult),
|
||||
Error(String),
|
||||
}
|
||||
|
||||
fn find_widget_by_type<T: IsA<gtk::Widget>>(page: &adw::NavigationPage) -> Option<T> {
|
||||
let result: RefCell<Option<T>> = RefCell::new(None);
|
||||
walk_widgets(&page.child(), &|widget| {
|
||||
if result.borrow().is_none()
|
||||
&& let Some(w) = widget.downcast_ref::<T>()
|
||||
{
|
||||
*result.borrow_mut() = Some(w.clone());
|
||||
}
|
||||
});
|
||||
result.into_inner()
|
||||
}
|
||||
|
||||
fn walk_widgets(widget: &Option<gtk::Widget>, f: &dyn Fn(>k::Widget)) {
|
||||
let Some(w) = widget else { return };
|
||||
f(w);
|
||||
let mut child = w.first_child();
|
||||
while let Some(c) = child {
|
||||
walk_widgets(&Some(c.clone()), f);
|
||||
child = c.next_sibling();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn format_bytes(bytes: u64) -> String {
|
||||
if bytes < 1024 {
|
||||
format!("{} B", bytes)
|
||||
} else if bytes < 1024 * 1024 {
|
||||
format!("{:.1} KB", bytes as f64 / 1024.0)
|
||||
} else if bytes < 1024 * 1024 * 1024 {
|
||||
format!("{:.1} MB", bytes as f64 / (1024.0 * 1024.0))
|
||||
} else {
|
||||
format!("{:.1} GB", bytes as f64 / (1024.0 * 1024.0 * 1024.0))
|
||||
}
|
||||
}
|
||||
|
||||
fn format_duration(ms: u64) -> String {
|
||||
if ms < 1000 {
|
||||
format!("{}ms", ms)
|
||||
} else if ms < 60_000 {
|
||||
format!("{:.1}s", ms as f64 / 1000.0)
|
||||
} else {
|
||||
let mins = ms / 60_000;
|
||||
let secs = (ms % 60_000) / 1000;
|
||||
format!("{}m {}s", mins, secs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use adw::prelude::*;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn build_processing_page() -> adw::NavigationPage {
|
||||
let content = gtk::Box::builder()
|
||||
.orientation(gtk::Orientation::Vertical)
|
||||
@@ -98,7 +97,6 @@ pub fn build_processing_page() -> adw::NavigationPage {
|
||||
.build()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn build_results_page() -> adw::NavigationPage {
|
||||
let scrolled = gtk::ScrolledWindow::builder()
|
||||
.hscrollbar_policy(gtk::PolicyType::Never)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use adw::prelude::*;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn build_settings_dialog() -> adw::PreferencesDialog {
|
||||
let dialog = adw::PreferencesDialog::builder()
|
||||
.title("Settings")
|
||||
|
||||
Reference in New Issue
Block a user