From f7c67c1a79215e63b85673d5b4f0e2bf34960797 Mon Sep 17 00:00:00 2001 From: burnintuna Date: Fri, 24 Jul 2026 23:44:49 +0900 Subject: [PATCH] Release v0.2.10 --- AGENTS.md | 3 +- Cargo.lock | 2 +- Cargo.toml | 2 +- ...ithub.comicviewer.Comicviewer.metainfo.xml | 1 + docs/INSTALL.ko.md | 1 + docs/RELEASING.ko.md | 8 ++--- src/config.rs | 17 ++++++++++ src/viewer.rs | 34 +++++++++++++++---- src/window.rs | 34 +++++++++++++++++-- 9 files changed, 87 insertions(+), 15 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index a550f62..39e7884 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -42,7 +42,8 @@ standalone images, and videos. entries when the search text is cleared. - Provide 100%, fit-width, fit-height, two-page, and continuous vertical viewing. - Support both right-to-left and left-to-right two-page layouts, plus controls - that advance by one page to adjust page pairing. + that advance by one page to adjust page pairing. Persist the last selected + two-page direction and restore it in newly opened archive viewers. - Reuse viewer windows when moving between ZIP, image, and video files. Preserve fullscreen state and keep the registered GTK titlebar widget stable while the displayed header changes. diff --git a/Cargo.lock b/Cargo.lock index e6712b2..ab112d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -182,7 +182,7 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "comicviewer" -version = "0.2.9" +version = "0.2.10" dependencies = [ "anyhow", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 786a6e5..9c16b80 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "comicviewer" -version = "0.2.9" +version = "0.2.10" edition = "2024" description = "A comic archive viewer for local and remote locations" license = "GPL-3.0-or-later" diff --git a/data/io.github.comicviewer.Comicviewer.metainfo.xml b/data/io.github.comicviewer.Comicviewer.metainfo.xml index 93a76d1..72ca04c 100644 --- a/data/io.github.comicviewer.Comicviewer.metainfo.xml +++ b/data/io.github.comicviewer.Comicviewer.metainfo.xml @@ -26,6 +26,7 @@ + diff --git a/docs/INSTALL.ko.md b/docs/INSTALL.ko.md index 3f749dc..eaf3a2b 100644 --- a/docs/INSTALL.ko.md +++ b/docs/INSTALL.ko.md @@ -96,6 +96,7 @@ Comicviewer는 XDG 표준 경로를 사용합니다. - About 창의 `업데이트`: Cafe24에서 최신 번들을 내려받아 검증 후 사용자 영역에 설치 - 이미지에서 `+`와 `-`: 현재 화면의 이미지를 임시로 10%씩 확대 또는 축소 - 이미지에서 `Ctrl` + 마우스 휠: 현재 화면의 이미지를 임시로 10%씩 확대 또는 축소 +- 2페이지의 `좌→우`/`우→좌` 방향은 마지막 선택을 저장해 다음 뷰어에서 복원 - 전체 화면에서 마우스 클릭: 숨겨진 컨트롤을 5초 동안 표시 - 전체 화면에서 `←`, `→`, `↑`, `↓`: 컨트롤을 즉시 숨기고 해당 키 동작 실행 - 파일 목록의 더블클릭 또는 선택 후 `Enter`: 해당 파일 열기 diff --git a/docs/RELEASING.ko.md b/docs/RELEASING.ko.md index d56de51..4a16848 100644 --- a/docs/RELEASING.ko.md +++ b/docs/RELEASING.ko.md @@ -55,7 +55,7 @@ AppImage 빌드가 끝나면 다음 스크립트로 체크섬과 통합 `tar.gz` 스크립트는 `Cargo.toml`에서 버전을 읽고 다음 파일을 생성합니다. -- `dist/Comicviewer-0.2.9-x86_64.AppImage.sha256` +- `dist/Comicviewer-0.2.10-x86_64.AppImage.sha256` - `dist/Comicviewer-latest-x86_64.tar.gz` - `dist/Comicviewer-latest-x86_64.tar.gz.sha256` @@ -78,10 +78,10 @@ sha256sum -c Comicviewer-latest-x86_64.tar.gz.sha256 ```sh git add Cargo.toml Cargo.lock data packaging README.md docs -git commit -m "Release v0.2.9" -git tag -a v0.2.9 -m "Comicviewer v0.2.9" +git commit -m "Release v0.2.10" +git tag -a v0.2.10 -m "Comicviewer v0.2.10" git push origin main -git push origin v0.2.9 +git push origin v0.2.10 ``` 이미 원격에 공개한 태그는 이동하거나 덮어쓰지 않습니다. 수정이 필요하면 새 diff --git a/src/config.rs b/src/config.rs index 9378f78..90c3473 100644 --- a/src/config.rs +++ b/src/config.rs @@ -12,6 +12,7 @@ pub struct AppConfig { pub fast_image_loading: bool, pub case_insensitive_sort: bool, pub viewer_mode: ViewerMode, + pub two_page_right_to_left: bool, pub locations: Vec, pub favorites: Vec, } @@ -23,6 +24,7 @@ impl Default for AppConfig { fast_image_loading: true, case_insensitive_sort: true, viewer_mode: ViewerMode::FitWidth, + two_page_right_to_left: true, locations: Vec::new(), favorites: Vec::new(), } @@ -179,6 +181,7 @@ mod tests { fast_image_loading: true, case_insensitive_sort: true, viewer_mode: ViewerMode::FitWidth, + two_page_right_to_left: true, locations: vec![LocationProfile { name: "테스트".into(), kind: LocationKind::Sftp, @@ -215,6 +218,7 @@ mod tests { assert!(decoded.fast_image_loading); assert!(decoded.case_insensitive_sort); assert_eq!(decoded.viewer_mode, ViewerMode::FitWidth); + assert!(decoded.two_page_right_to_left); assert!(decoded.favorites.is_empty()); } @@ -231,4 +235,17 @@ mod tests { } assert_eq!(ViewerMode::from_dropdown_index(99), ViewerMode::FitWidth); } + + #[test] + fn two_page_direction_defaults_to_rtl_and_round_trips_ltr() { + let existing: AppConfig = toml::from_str("cache_quota_gb = 10\n").unwrap(); + assert!(existing.two_page_right_to_left); + + let config = AppConfig { + two_page_right_to_left: false, + ..AppConfig::default() + }; + let decoded: AppConfig = toml::from_str(&toml::to_string(&config).unwrap()).unwrap(); + assert!(!decoded.two_page_right_to_left); + } } diff --git a/src/viewer.rs b/src/viewer.rs index 98bf9b9..52ad233 100644 --- a/src/viewer.rs +++ b/src/viewer.rs @@ -223,6 +223,11 @@ pub struct ViewModeControl { pub change: Rc, } +pub struct TwoPageDirectionControl { + pub selected: u32, + pub change: Rc bool>, +} + pub struct FileViewerControls { pub sibling_navigator: Option, pub favorite_control: FavoriteControl, @@ -230,6 +235,7 @@ pub struct FileViewerControls { pub file_drop_control: FileDropControl, pub reuse_window: Option, pub view_mode_control: Option, + pub two_page_direction_control: Option, } #[derive(Clone)] @@ -274,7 +280,10 @@ impl ViewerWindow { file_drop_control, reuse_window, view_mode_control: _, + two_page_direction_control, } = controls; + let direction_control = two_page_direction_control + .expect("archive viewer requires a two-page direction control"); let title = source.display_name.clone(); let file_list_query = reuse_window .as_ref() @@ -326,7 +335,8 @@ impl ViewerWindow { let page_label = gtk::Label::new(Some("- / -")); page_label.set_hexpand(true); let direction_dropdown = gtk::DropDown::from_strings(&["좌→우", "우→좌"]); - direction_dropdown.set_selected(1); + let direction_selected = direction_control.selected.min(1); + direction_dropdown.set_selected(direction_selected); let mode_dropdown = gtk::DropDown::from_strings(&[ "100%", "폭 맞춤", @@ -451,6 +461,8 @@ impl ViewerWindow { download_button, file_download: download_control.download, view_mode_change: view_mode_control.change, + direction_change: direction_control.change, + direction_selected: Cell::new(direction_selected), seek_hide_timer: RefCell::new(None), seek_interacting: Cell::new(false), last_pointer_position: Cell::new(None), @@ -620,6 +632,8 @@ struct ViewerController { download_button: gtk::Button, file_download: Rc, view_mode_change: Rc, + direction_change: Rc bool>, + direction_selected: Cell, seek_hide_timer: RefCell>, seek_interacting: Cell, last_pointer_position: Cell>, @@ -677,11 +691,18 @@ impl ViewerController { } }); let controller = Rc::downgrade(self); - self.direction_dropdown.connect_selected_notify(move |_| { - if let Some(controller) = controller.upgrade() { - controller.render(); - } - }); + self.direction_dropdown + .connect_selected_notify(move |dropdown| { + if let Some(controller) = controller.upgrade() { + let selected = dropdown.selected().min(1); + if (controller.direction_change)(selected) { + controller.direction_selected.set(selected); + controller.render(); + } else { + dropdown.set_selected(controller.direction_selected.get()); + } + } + }); let controller = Rc::downgrade(self); self.favorite_button.connect_clicked(move |_| { if let Some(controller) = controller.upgrade() { @@ -2184,6 +2205,7 @@ impl MediaViewerWindow { file_drop_control, reuse_window, view_mode_control, + two_page_direction_control: _, } = controls; let title = source.display_name.clone(); let file_list_query = reuse_window diff --git a/src/window.rs b/src/window.rs index e9fdfd3..57be341 100644 --- a/src/window.rs +++ b/src/window.rs @@ -30,8 +30,8 @@ use crate::source::{ }; use crate::viewer::{ FavoriteControl, FileDownloadControl, FileDropControl, FileViewerControls, MediaViewerWindow, - SiblingFile, SiblingFileActions, SiblingNavigator, SiblingTarget, ViewModeControl, - ViewerWindow, install_file_drop_target, set_viewer_header, set_viewer_loading, + SiblingFile, SiblingFileActions, SiblingNavigator, SiblingTarget, TwoPageDirectionControl, + ViewModeControl, ViewerWindow, install_file_drop_target, set_viewer_header, set_viewer_loading, }; use crate::zip_cache::ZipCache; @@ -997,6 +997,7 @@ impl BrowserController { let Some(application) = controller.window.application() else { return; }; + let direction_control = controller.two_page_direction_control(); ViewerWindow::open( &application, source, @@ -1011,6 +1012,7 @@ impl BrowserController { file_drop_control: controller.file_drop_control(), reuse_window, view_mode_control: None, + two_page_direction_control: Some(direction_control), }, ); } @@ -1075,6 +1077,7 @@ impl BrowserController { file_drop_control: controller.file_drop_control(), reuse_window, view_mode_control, + two_page_direction_control: None, }, ); } @@ -1461,6 +1464,33 @@ impl BrowserController { } } + fn two_page_direction_control(self: &Rc) -> TwoPageDirectionControl { + let selected = u32::from(self.config.borrow().two_page_right_to_left); + let controller = Rc::downgrade(self); + TwoPageDirectionControl { + selected, + change: Rc::new(move |index| { + let Some(controller) = controller.upgrade() else { + return false; + }; + let right_to_left = index == 1; + if controller.config.borrow().two_page_right_to_left == right_to_left { + return true; + } + let mut updated = controller.config.borrow().clone(); + updated.two_page_right_to_left = right_to_left; + if let Err(error) = updated.save(&controller.config_path) { + controller + .status_label + .set_text(&format!("2페이지 방향을 저장할 수 없습니다: {error:#}")); + return false; + } + *controller.config.borrow_mut() = updated; + true + }), + } + } + fn toggle_favorite(&self, entry: &BrowserEntry, profile: &LocationProfile) -> bool { let mut updated = self.config.borrow().clone(); let was_active = self.is_favorite(&entry.uri);