Skip to content

Commit e986fb3

Browse files
committed
Add tab history menu on right click
1 parent 5b812f3 commit e986fb3

File tree

4 files changed

+207
-90
lines changed

4 files changed

+207
-90
lines changed

data/resources/ui/window.blp

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,27 @@ template GeopardWindow: Adw.ApplicationWindow {
1111
transition-type: crossfade;
1212
Adw.HeaderBar {
1313
[start]
14-
Gtk.Button {
15-
icon-name: "go-previous-symbolic";
16-
action-name: "win.previous";
14+
Gtk.Box {
15+
Gtk.Button previous {
16+
icon-name: "go-previous-symbolic";
17+
action-name: "win.previous";
18+
}
19+
Gtk.Popover previous_popover {
20+
autohide: true;
21+
has-arrow: false;
22+
}
1723
}
24+
1825
[start]
19-
Gtk.Button {
20-
icon-name: "go-next-symbolic";
21-
action-name: "win.next";
26+
Gtk.Box {
27+
Gtk.Button next {
28+
icon-name: "go-next-symbolic";
29+
action-name: "win.next";
30+
}
31+
Gtk.Popover next_popover {
32+
autohide: true;
33+
has-arrow: false;
34+
}
2235
}
2336
[start]
2437
Gtk.Button {

src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,11 @@ fn main() {
8585
application
8686
.connect_activate(move |app| app.open(&[gio::File::for_uri(bookmarks_url().as_str())], ""));
8787
application.connect_open(move |app, files, _| {
88-
let window = widgets::Window::new(&app, config.clone());
88+
let window = widgets::Window::new(app, config.clone());
8989
window.present();
9090
windows.borrow_mut().push(window.clone());
9191
for f in files {
92-
gtk::prelude::WidgetExt::activate_action(&window, "win.new-tab", None).unwrap();
92+
gtk::prelude::WidgetExt::activate_action(&window, "win.new-empty-tab", None).unwrap();
9393
gtk::prelude::WidgetExt::activate_action(
9494
&window,
9595
"win.open-url",

src/widgets/tab.rs

Lines changed: 101 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use anyhow::{bail, Context, Result};
22
use async_fs::File;
3+
use async_trait::async_trait;
34
use futures::future::RemoteHandle;
45
use futures::io::BufReader;
56
use futures::prelude::*;
@@ -11,9 +12,9 @@ use gtk::prelude::*;
1112
use gtk::subclass::prelude::*;
1213
use gtk::CompositeTemplate;
1314
use gtk::TemplateChild;
14-
use log::{debug, info};
15+
use log::{debug, info, warn};
1516
use once_cell::sync::Lazy;
16-
use std::cell::{Cell, RefCell};
17+
use std::cell::{Cell, Ref, RefCell};
1718
use std::fmt::Write;
1819
use std::marker::PhantomData;
1920
use std::pin::Pin;
@@ -28,7 +29,7 @@ use hypertext::HypertextEvent;
2829

2930
const BYTES_BEFORE_YIELD: usize = 1024 * 10;
3031

31-
#[derive(Debug, Clone, PartialEq)]
32+
#[derive(Clone)]
3233
pub struct HistoryItem {
3334
pub url: url::Url,
3435
pub cache: Rc<RefCell<Option<Vec<u8>>>>,
@@ -42,17 +43,55 @@ pub struct HistoryStatus {
4243
pub(crate) available: usize,
4344
}
4445

46+
#[derive(Default)]
47+
pub struct History {
48+
items: Vec<HistoryItem>,
49+
index: Option<usize>,
50+
}
51+
52+
impl History {
53+
fn push(&mut self, item: HistoryItem) -> usize {
54+
let new_index = self.index.map_or(0, |i| i + 1);
55+
self.index = Some(new_index);
56+
self.items.truncate(new_index);
57+
self.items.push(item);
58+
new_index
59+
}
60+
fn index(&self) -> Option<usize> {
61+
self.index
62+
}
63+
fn len(&self) -> usize {
64+
self.items.len()
65+
}
66+
fn current(&self) -> Option<&HistoryItem> {
67+
self.index.map(|i| &self.items[i])
68+
}
69+
fn items(&self) -> &[HistoryItem] {
70+
&self.items
71+
}
72+
fn set_index(&mut self, i: usize) -> bool {
73+
if (0..self.items.len()).contains(&i) {
74+
self.index = Some(i);
75+
true
76+
} else {
77+
false
78+
}
79+
}
80+
fn go_previous(&mut self) -> bool {
81+
self.set_index(self.index.unwrap_or(0).saturating_sub(1))
82+
}
83+
}
84+
4585
pub mod imp {
4686

4787
pub use super::*;
48-
#[derive(Debug, Default, Properties, CompositeTemplate)]
88+
#[derive(Default, Properties, CompositeTemplate)]
4989
#[template(resource = "/com/ranfdev/Geopard/ui/tab.ui")]
5090
#[properties(wrapper_type = super::Tab)]
5191
pub struct Tab {
5292
pub(crate) gemini_client: RefCell<gemini::Client>,
5393
pub(crate) config: RefCell<crate::config::Config>,
54-
pub(crate) history: RefCell<Vec<HistoryItem>>,
55-
pub(crate) current_hi: Cell<Option<usize>>,
94+
pub(crate) history: RefCell<History>,
5695
#[template_child]
5796
pub(crate) scroll_win: TemplateChild<gtk::ScrolledWindow>,
5897
#[template_child]
@@ -120,9 +159,10 @@ pub mod imp {
120159
}
121160
impl Tab {
122161
fn history_status(&self) -> HistoryStatus {
162+
let history = self.history.borrow();
123163
HistoryStatus {
124-
current: self.current_hi.get().unwrap_or(0),
125-
available: self.history.borrow().len(),
164+
current: history.index().unwrap_or(0),
165+
available: history.len(),
126166
}
127167
}
128168
}
@@ -149,48 +189,44 @@ impl Tab {
149189

150190
// If there's an in flight request, the related history item (the last one)
151191
// must be removed
152-
if let Some(in_flight_req) = imp.req_handle.borrow_mut().take() {
153-
if in_flight_req.now_or_never().is_none() {
154-
imp.history.borrow_mut().pop();
155-
let i = imp.current_hi.get().unwrap();
156-
imp.current_hi.replace(Some(i.saturating_sub(1)));
192+
{
193+
let req = imp.req_handle.take();
194+
if let Some(req) = req {
195+
// if the request isn't ready, it's still in flight
196+
if req.now_or_never().is_none() {
197+
imp.history.borrow_mut().go_previous();
198+
}
157199
}
158200
}
159201

160-
let i = self.add_to_history(HistoryItem {
202+
let body: Rc<RefCell<Option<Vec<u8>>>> = Rc::new(RefCell::new(None));
203+
let body_weak = Rc::downgrade(&body);
204+
205+
self.add_to_history(HistoryItem {
161206
url: url.clone(),
162-
cache: Default::default(),
207+
cache: body,
163208
scroll_progress: 0.0,
164209
});
165-
let cache_space = Rc::downgrade(&self.imp().history.borrow()[i].cache);
210+
166211
let this = self.clone();
167212
let fut = async move {
168-
let cache = this.open_url(url).await;
169-
cache_space.upgrade().map(|rc| rc.replace(cache));
213+
let data = this.open_url(url).await;
214+
*body_weak.upgrade().unwrap().borrow_mut() = data;
170215
};
171-
self.spawn_request(fut);
216+
imp.req_handle
217+
.replace(Some(glibctx().spawn_local_with_handle(fut).unwrap()));
172218
}
173-
// FIXME: make history functions simpler
174-
fn add_to_history(&self, item: HistoryItem) -> usize {
219+
fn add_to_history(&self, mut item: HistoryItem) -> usize {
175220
let imp = self.imp();
176-
let i = {
177-
let mut history = imp.history.borrow_mut();
178-
let i = imp.current_hi.get();
179-
if let Some(i) = i {
180-
let scroll_progress = imp.scroll_win.vadjustment().value();
181-
if let Some(item) = history.get_mut(i) {
182-
item.scroll_progress = scroll_progress;
183-
}
184-
history.truncate(i + 1);
185-
};
186-
history.push(item);
187-
let i = history.len() - 1;
188-
imp.current_hi.replace(Some(i));
189-
i
190-
};
221+
222+
item.scroll_progress = imp.scroll_win.vadjustment().value();
223+
{
224+
imp.history.borrow_mut().push(item);
225+
}
226+
191227
self.emit_history_status();
192228
self.log_history_position();
193-
i
229+
imp.history.borrow().index().unwrap()
194230
}
195231
fn clear_stack_widgets(&self) {
196232
let imp = self.imp();
@@ -241,6 +277,7 @@ impl Tab {
241277
}
242278
fn open_history(&self, item: HistoryItem) -> Pin<Box<dyn Future<Output = ()>>> {
243279
let HistoryItem { url, cache, .. } = item;
280+
244281
let cache = cache.borrow();
245282
match &*cache {
246283
Some(cache) => Box::pin(self.open_cached(url, cache.clone())),
@@ -272,48 +309,19 @@ impl Tab {
272309
}
273310
}
274311
fn log_history_position(&self) {
275-
let i = self.imp().current_hi.get();
312+
let i = self.imp().history.borrow().index();
276313
info!("history position: {i:?}");
277314
}
278-
pub fn previous(&self) -> Result<()> {
279-
let imp = self.imp();
280-
let i = {
281-
imp.current_hi
282-
.get()
283-
.and_then(|i| i.checked_sub(1))
284-
.context("going back in history")?
285-
};
286-
imp.current_hi.replace(Some(i));
287-
self.log_history_position();
288-
self.emit_history_status();
289-
290-
let h = { imp.history.borrow_mut().get(i).cloned() };
291-
h.map(|x| self.spawn_request(self.open_history(x)))
292-
.context("retrieving previous item from history")
315+
pub fn previous(&self) -> bool {
316+
self.move_in_history(-1)
293317
}
294-
pub fn next(&self) -> Result<()> {
295-
let imp = self.imp();
296-
let i = {
297-
imp.current_hi
298-
.get()
299-
.map(|i| i + 1)
300-
.filter(|i| *i < imp.history.borrow().len())
301-
.context("going forward in history")?
302-
};
303-
imp.current_hi.replace(Some(i));
304-
self.log_history_position();
305-
self.emit_history_status();
306-
307-
let h = { imp.history.borrow_mut().get(i).cloned() };
308-
h.map(|x| self.spawn_request(self.open_history(x)))
309-
.context("retrieving next item from history")
318+
pub fn next(&self) -> bool {
319+
self.move_in_history(1)
310320
}
311321
pub fn reload(&self) {
312322
let imp = self.imp();
313-
let i = imp.current_hi.get().unwrap();
314323

315-
if let Some(h) = imp.history.borrow_mut().get(i) {
316-
h.cache.replace(None);
324+
if let Some(h) = imp.history.borrow_mut().current() {
317325
self.spawn_request(self.open_history(h.clone()));
318326
}
319327
}
@@ -664,4 +672,25 @@ impl Tab {
664672
imp.stack.add_child(&p);
665673
imp.stack.set_visible_child(&p);
666674
}
675+
pub fn history_items(&self) -> Ref<[HistoryItem]> {
676+
Ref::map(self.imp().history.borrow(), |x| x.items())
677+
}
678+
pub fn move_in_history(&self, offset: isize) -> bool {
679+
let moved = {
680+
let mut h = self.imp().history.borrow_mut();
681+
let new_index = if offset > 0 {
682+
h.index().unwrap_or(0) + offset as usize
683+
} else {
684+
h.index().unwrap_or(0).saturating_sub(offset.abs() as usize)
685+
};
686+
h.index() != Some(new_index) && h.set_index(new_index)
687+
};
688+
if moved {
689+
self.spawn_request(
690+
self.open_history(self.imp().history.borrow().current().unwrap().clone()),
691+
);
692+
self.emit_history_status();
693+
}
694+
moved
695+
}
667696
}

0 commit comments

Comments
 (0)