Improve viewer controls and navigation

This commit is contained in:
burnintuna 2026-07-22 04:09:30 +09:00
parent b139395067
commit ffa8bc93ba
2 changed files with 477 additions and 16 deletions

View file

@ -248,6 +248,44 @@ impl BrowserController {
let controller = Rc::clone(self);
self.location_dropdown
.connect_selected_notify(move |_| controller.select_profile());
let location_factory = gtk::SignalListItemFactory::new();
let controller = Rc::downgrade(self);
location_factory.connect_setup(move |_, item| {
let Some(item) = item.downcast_ref::<gtk::ListItem>() else {
return;
};
let label = gtk::Label::builder().xalign(0.0).build();
let click = gtk::GestureClick::builder().button(1).build();
item.set_child(Some(&label));
let item = item.downgrade();
let controller = controller.clone();
click.connect_released(move |_, _, _, _| {
let Some(item) = item.upgrade() else {
return;
};
let Some(controller) = controller.upgrade() else {
return;
};
if is_favorites_selection(item.position(), controller.profiles().len()) {
controller.show_favorites();
}
});
label.add_controller(click);
});
location_factory.connect_bind(|_, item| {
let Some(item) = item.downcast_ref::<gtk::ListItem>() else {
return;
};
let Some(label) = item.child().and_downcast::<gtk::Label>() else {
return;
};
let Some(value) = item.item().and_downcast::<gtk::StringObject>() else {
return;
};
label.set_text(value.string().as_str());
});
self.location_dropdown
.set_list_factory(Some(&location_factory));
let controller = Rc::clone(self);
self.search_entry
.connect_search_changed(move |_| controller.render());
@ -274,8 +312,14 @@ impl BrowserController {
let key_controller = gtk::EventControllerKey::new();
let controller = Rc::clone(self);
key_controller
.connect_key_pressed(move |_, key, _, modifiers| controller.typeahead(key, modifiers));
key_controller.connect_key_pressed(move |_, key, _, modifiers| {
if key == gtk::gdk::Key::BackSpace {
controller.go_up();
glib::Propagation::Stop
} else {
controller.typeahead(key, modifiers)
}
});
self.list_box.add_controller(key_controller);
let controller = Rc::clone(self);
@ -315,7 +359,7 @@ impl BrowserController {
fn select_profile(self: &Rc<Self>) {
let profile_index = self.location_dropdown.selected() as usize;
let profiles = self.profiles();
if profile_index == profiles.len() {
if is_favorites_selection(self.location_dropdown.selected(), profiles.len()) {
self.show_favorites();
return;
}
@ -348,7 +392,6 @@ impl BrowserController {
self.delete_location_button.set_sensitive(false);
self.path_entry.set_text("즐겨찾기");
self.path_entry.set_editable(false);
self.search_entry.set_text("");
let entries = self
.config
.borrow()
@ -357,10 +400,14 @@ impl BrowserController {
.map(favorite_browser_entry)
.collect();
let mut state = self.state.borrow_mut();
state.generation = state.generation.wrapping_add(1);
state.typeahead_query.clear();
state.typeahead_last_input = None;
state.browsing_favorites = true;
state.favorite_profile = None;
state.entries = entries;
drop(state);
self.search_entry.set_text("");
self.render();
}
@ -500,7 +547,10 @@ impl BrowserController {
let Some(controller) = controller.upgrade() else {
return;
};
if controller.state.borrow().generation != generation {
let state = controller.state.borrow();
let stale = state.generation != generation || state.browsing_favorites;
drop(state);
if stale {
return;
}
match result {
@ -815,6 +865,7 @@ impl BrowserController {
favorite_control,
download_control,
reuse_window,
view_mode_control: None,
},
);
}
@ -846,6 +897,7 @@ impl BrowserController {
let navigator = self.sibling_navigator(&entry, siblings, profile.clone());
let favorite_control = self.favorite_control(&entry, &profile);
let download_control = self.download_control(&entry);
let view_mode_control = (kind == EntryKind::Image).then(|| self.view_mode_control());
self.zip_cache.prepare_media(
entry,
move |current, total| {
@ -868,6 +920,7 @@ impl BrowserController {
favorite_control,
download_control,
reuse_window,
view_mode_control,
},
);
}
@ -1166,7 +1219,7 @@ impl BrowserController {
.transient_for(&self.window)
.modal(true)
.resizable(false)
.default_width(360)
.default_width(520)
.build();
let content = gtk::Box::new(gtk::Orientation::Vertical, 14);
content.set_margin_top(24);
@ -1179,12 +1232,21 @@ impl BrowserController {
)));
name.add_css_class("title-2");
let contact = gtk::Label::new(Some("개발자 연락처 : burningtuna@gmail.com"));
let libraries = gtk::Label::new(Some(
"사용한 라이브러리\n\
GTK4 / GIO · GStreamer · image · zip · rusqlite / SQLite\n\
serde · toml · anyhow · chrono · filetime · percent-encoding\n\
sha2 · tempfile",
));
libraries.set_xalign(0.0);
libraries.set_wrap(true);
let close_button = gtk::Button::with_label("닫기");
close_button.set_halign(gtk::Align::End);
let dialog_to_close = dialog.clone();
close_button.connect_clicked(move |_| dialog_to_close.close());
content.append(&name);
content.append(&contact);
content.append(&libraries);
content.append(&close_button);
dialog.set_child(Some(&content));
dialog.present();
@ -1449,6 +1511,10 @@ fn navigable_sibling(current: EntryKind, candidate: EntryKind) -> bool {
}
}
fn is_favorites_selection(selected: u32, profile_count: usize) -> bool {
selected != gtk::INVALID_LIST_POSITION && selected as usize == profile_count
}
fn sorted_navigable_entries(
entries: &[BrowserEntry],
current_kind: EntryKind,
@ -1634,6 +1700,9 @@ mod tests {
assert_eq!(folder.kind, EntryKind::Directory);
assert_eq!(archive.kind, EntryKind::ZipArchive);
assert!(is_favorites_selection(3, 3));
assert!(!is_favorites_selection(2, 3));
assert!(!is_favorites_selection(gtk::INVALID_LIST_POSITION, 3));
}
#[test]