T21b and T21c round 1 fixes

- a page centers only while it fits the view; a wider one starts in reach
- the reveal runs after the pages' prepaint and keys on the hit itself, so a
  new hit at the same index and a hit current from the first frame come into view
- the chapter list scrolls
- zen takes focus when turned on from outside, so Escape leaves it, and hands
  focus back
- the gallery keeps a page's due date and tags
This commit is contained in:
ZacharyZhang-NY committed 2026-09-26 04:55:58 -04:00
1 parent 9854cfa460
commit 444a95a854
8 files changed
+196 -42

No files matched your search

+126 -16
View File
@@ -1,23 +1,33 @@
use std::{cell::RefCell, rc::Rc};
use gpui::{
AppContext as _, Bounds, Context, Entity, IntoElement, Modifiers, ParentElement, Pixels, Point,
Render, ScrollDelta, ScrollWheelEvent, Styled, TestAppContext, TouchPhase, VisualTestContext,
Window, div, point, px, size,
AppContext as _, Bounds, Context, Entity, Focusable, IntoElement, Modifiers, ParentElement,
Pixels, Point, Render, ScrollDelta, ScrollWheelEvent, Styled, TestAppContext, TouchPhase,
VisualTestContext, Window, div, point, px, size,
};
use super::{DocPage, DocumentViewer, PageHit};
use super::{DocPage, DocumentViewer, PageHit, ZenMode};
use crate::{
editor::FindWidget,
forms::TextInput,
forms::{Input, TextInput},
theme::{ActiveTheme, ControlSize, Theme},
};
type Noted = Rc<RefCell<Option<(usize, Point<f32>)>>>;
/// Three tall pages with one hit on the second; a press while pinning records the note asked.
/// A hit on `page`, 400 points down.
fn hit(page: usize) -> PageHit {
PageHit {
page,
bounds: Bounds::new(point(40.0, 400.0), size(60.0, 12.0)),
}
}
/// Three tall pages and the hits found; a press while pinning records the note asked.
struct Reader {
query: Entity<TextInput>,
width: f32,
hits: Vec<PageHit>,
current: Option<usize>,
noted: Noted,
}
@@ -27,20 +37,16 @@ impl Render for Reader {
let pages: Vec<DocPage> = (0..3)
.map(|ix| DocPage {
source: format!("page-{ix}.png").into(),
width: 400.0,
width: self.width,
height: 1000.0,
})
.collect();
let hit = PageHit {
page: 1,
bounds: Bounds::new(point(40.0, 400.0), size(60.0, 12.0)),
};
let noted = self.noted.clone();
div().size_full().child(
DocumentViewer::new("viewer", "lift.pdf", pages)
.find(
FindWidget::new("find", &self.query, self.current, 1),
vec![hit],
FindWidget::new("find", &self.query, self.current, self.hits.len()),
self.hits.clone(),
self.current,
)
.notes(Vec::new(), move |page, at, _, _| {
@@ -51,12 +57,23 @@ impl Render for Reader {
}
fn open(cx: &mut TestAppContext) -> (Entity<Reader>, Noted, &mut VisualTestContext) {
open_with(400.0, None, cx)
}
/// Pages `width` points wide, with a hit on the second, current from the first frame when `current` says.
fn open_with(
width: f32,
current: Option<usize>,
cx: &mut TestAppContext,
) -> (Entity<Reader>, Noted, &mut VisualTestContext) {
cx.update(Theme::init);
let noted = Noted::default();
let seen = noted.clone();
let (reader, cx) = cx.add_window_view(move |window, cx| Reader {
query: cx.new(|cx| TextInput::new(window, cx)),
current: None,
width,
hits: vec![hit(1)],
current,
noted: seen,
});
settle(cx);
@@ -85,8 +102,13 @@ fn wheel(down: f32, cx: &mut VisualTestContext) {
settle(cx);
}
/// Turns pinning on with the header's last button, then presses the pages; returns the note asked.
/// Turns pinning on with the header's last button, then presses the pages at their middle; returns the note asked.
fn pin(cx: &mut VisualTestContext, noted: &Noted) -> (usize, Point<f32>) {
let at = on_pages(cx);
pin_at(at, cx, noted)
}
fn pin_at(at: Point<Pixels>, cx: &mut VisualTestContext, noted: &Noted) -> (usize, Point<f32>) {
let toggle = cx.update(|window, cx| {
let header = cx
.theme()
@@ -99,7 +121,6 @@ fn pin(cx: &mut VisualTestContext, noted: &Noted) -> (usize, Point<f32>) {
});
cx.simulate_click(toggle, Modifiers::none());
settle(cx);
let at = on_pages(cx);
cx.simulate_click(at, Modifiers::none());
settle(cx);
noted
@@ -143,3 +164,92 @@ fn a_current_hit_comes_to_one_place_from_any_scroll(cx: &mut TestAppContext) {
assert_eq!(from_top.0, 1, "the hit's page comes into view");
assert_eq!(from_top, from_below);
}
#[gpui::test]
fn a_page_wider_than_the_view_starts_at_its_left_edge(cx: &mut TestAppContext) {
let (_, noted, cx) = open_with(3000.0, None, cx);
let (page, at) = pin_at(point(px(60.0), px(300.0)), cx, &noted);
assert_eq!(page, 0);
assert!(
at.x < 100.0,
"the page's left edge is in reach, not at {}",
at.x
);
}
#[gpui::test]
fn a_new_hit_at_the_same_index_moves_the_view(cx: &mut TestAppContext) {
let (reader, noted, cx) = open(cx);
reader.update(cx, |reader, cx| {
reader.current = Some(0);
cx.notify();
});
settle(cx);
reader.update(cx, |reader, cx| {
reader.hits = vec![hit(2)];
cx.notify();
});
settle(cx);
assert_eq!(pin(cx, &noted).0, 2, "the view follows the new hit");
}
#[gpui::test]
fn a_hit_current_from_the_first_frame_comes_into_view(cx: &mut TestAppContext) {
let (_, noted, first) = open_with(400.0, Some(0), cx);
let at_once = pin(first, &noted);
let later = revealed(0.0, cx);
assert_eq!(at_once, later);
}
/// A field outside a zen view, and how often Escape left zen.
struct Zen {
outside: Entity<TextInput>,
on: bool,
left: usize,
}
impl Render for Zen {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let me = cx.entity();
div().size_full().child(Input::new(&self.outside)).child(
ZenMode::new("zen", self.on)
.on_exit(move |_, cx| {
me.update(cx, |zen, cx| {
zen.on = false;
zen.left += 1;
cx.notify();
})
})
.child(div().child("A quiet page")),
)
}
}
#[gpui::test]
fn escape_leaves_zen_turned_on_from_outside_and_hands_focus_back(cx: &mut TestAppContext) {
cx.update(|cx| {
Theme::init(cx);
crate::forms::bind_keys(cx);
});
let (zen, cx) = cx.add_window_view(|window, cx| Zen {
outside: cx.new(|cx| TextInput::new(window, cx)),
on: false,
left: 0,
});
cx.update(|window, cx| {
window.activate_window();
let outside = zen.read(cx).outside.focus_handle(cx);
window.focus(&outside);
});
settle(cx);
zen.update(cx, |zen, cx| {
zen.on = true;
cx.notify();
});
settle(cx);
cx.simulate_keystrokes("escape");
settle(cx);
assert_eq!(zen.read_with(cx, |zen, _| zen.left), 1);
let back = cx.update(|window, cx| zen.read(cx).outside.focus_handle(cx).is_focused(window));
assert!(back, "focus returns to where it was");
}