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:
1 parent
9854cfa460
commit
444a95a854
8 files changed
+196
-42
No files matched your search
@@ -112,6 +112,8 @@ Ely GPUI Component. A component library for GPUI, in light and dark.
|
|||||||
- Tokens live in `src/theme`; the raw `px()` scan skips that folder only.
|
- Tokens live in `src/theme`; the raw `px()` scan skips that folder only.
|
||||||
- `ScrollHandle` keeps its children's bounds unscrolled: a child paints at its bounds plus the offset. A press on a scrolled child subtracts both, and an offset that brings a child to a place is set whole, not added (`documents::find::reveal`).
|
- `ScrollHandle` keeps its children's bounds unscrolled: a child paints at its bounds plus the offset. A press on a scrolled child subtracts both, and an offset that brings a child to a place is set whole, not added (`documents::find::reveal`).
|
||||||
- A find box is `editor::FindWidget`, over code, a terminal or a document's pages. It shows the replace fold and the option toggles only when the owner handles them.
|
- A find box is `editor::FindWidget`, over code, a terminal or a document's pages. It shows the replace fold and the option toggles only when the owner handles them.
|
||||||
|
- Taffy 0.9 hands negative free space to auto margins and centering alike, so a centered child wider than its scroll box starts past the scroll's reach. Center only what fits the measured box (`documents::DocumentViewer`).
|
||||||
|
- A mode that leaves on Escape keeps its own focus handle: turned on while focus is elsewhere, it takes focus so Escape reaches it, and hands focus back when it leaves (`documents::ZenMode`).
|
||||||
- A callback field holds a named `type` alias; clippy's type_complexity rejects `Option<Rc<dyn Fn(..)>>`. A chapter shares one alias per shape.
|
- A callback field holds a named `type` alias; clippy's type_complexity rejects `Option<Rc<dyn Fn(..)>>`. A chapter shares one alias per shape.
|
||||||
- A component that fills its container says `w_full` on its root. As a window's root, or in a flex row, it would shrink to its text's narrowest width.
|
- A component that fills its container says `w_full` on its root. As a window's root, or in a flex row, it would shrink to its text's narrowest width.
|
||||||
- A field's `Highlight` sets color, wash, weight, slant and strike per span; measuring and painting share one set of runs, so wrapped heights match.
|
- A field's `Highlight` sets color, wash, weight, slant and strike per span; measuring and painting share one set of runs, so wrapped heights match.
|
||||||
|
|||||||
@@ -250,3 +250,5 @@ Per-component lines live in `tasks/`. Tags there:
|
|||||||
| T21a | 1 | FAIL | Enter kept a selection; undo of a split left focus on a gone field; italic inside bold ate the bold; keys went into merged cells; undo kept a changed kind's highlighter |
|
| T21a | 1 | FAIL | Enter kept a selection; undo of a split left focus on a gone field; italic inside bold ate the bold; keys went into merged cells; undo kept a changed kind's highlighter |
|
||||||
| T21a | 2 | FAIL | undo of typing lost the selection it replaced; Up from below a table landed in a hidden merged cell |
|
| T21a | 2 | FAIL | undo of typing lost the selection it replaced; Up from below a table landed in a hidden merged cell |
|
||||||
| T21a | 3 | FAIL | undo after a delete or a paste lost the selection they replaced; fixed after the cap by recording the replaced selection where every TextInput edit passes, verified by a red-green test, no fourth review |
|
| T21a | 3 | FAIL | undo after a delete or a paste lost the selection they replaced; fixed after the cap by recording the replaced selection where every TextInput edit passes, verified by a red-green test, no fourth review |
|
||||||
|
| T21b | 1 | FAIL | a page zoomed wider than the view sat past its left edge; a new hit at the same index kept the old page in view; a hit current from the first frame only scrolled its page to the top; the chapter list did not scroll; Escape could not leave zen turned on from a button |
|
||||||
|
| T21c | 1 | FAIL | the gallery's page properties dropped a new due date and tags |
|
||||||
@@ -163,11 +163,20 @@ pub fn knowledge(window: &mut Window, cx: &mut App) -> impl IntoElement + use<>
|
|||||||
let icon = keep("know-icon", || SharedString::from("☀️"), window, cx);
|
let icon = keep("know-icon", || SharedString::from("☀️"), window, cx);
|
||||||
let status = keep("know-status", || SharedString::from("Drafting"), window, cx);
|
let status = keep("know-status", || SharedString::from("Drafting"), window, cx);
|
||||||
let done = keep("know-done", || false, window, cx);
|
let done = keep("know-done", || false, window, cx);
|
||||||
let (now_cover, now_icon, now_status, now_done) = (
|
let due = keep("know-due", || jiff::civil::date(2026, 10, 12), window, cx);
|
||||||
|
let tags = keep(
|
||||||
|
"know-tags",
|
||||||
|
|| vec![SharedString::from("color"), SharedString::from("theme")],
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
let (now_cover, now_icon, now_status, now_done, now_due, now_tags) = (
|
||||||
cover.read(cx).clone(),
|
cover.read(cx).clone(),
|
||||||
icon.read(cx).clone(),
|
icon.read(cx).clone(),
|
||||||
status.read(cx).clone(),
|
status.read(cx).clone(),
|
||||||
*done.read(cx),
|
*done.read(cx),
|
||||||
|
*due.read(cx),
|
||||||
|
tags.read(cx).clone(),
|
||||||
);
|
);
|
||||||
let trash = keep(
|
let trash = keep(
|
||||||
"know-trash",
|
"know-trash",
|
||||||
@@ -310,8 +319,8 @@ pub fn knowledge(window: &mut Window, cx: &mut App) -> impl IntoElement + use<>
|
|||||||
.selected(now_status)
|
.selected(now_status)
|
||||||
.on_change(move |next, _, cx| set(&status, next.clone(), cx)),
|
.on_change(move |next, _, cx| set(&status, next.clone(), cx)),
|
||||||
)
|
)
|
||||||
.row("Due", DatePicker::new("know-due", Some(jiff::civil::date(2026, 10, 12))))
|
.row("Due", DatePicker::new("know-due", Some(now_due)).on_change(move |next, _, cx| set(&due, next, cx)))
|
||||||
.row("Tags", TagInput::new("know-tags", ["color", "theme"]))
|
.row("Tags", TagInput::new("know-tags", now_tags).on_change(move |next, _, cx| set(&tags, next, cx)))
|
||||||
.row("Published", Checkbox::new("know-published", now_done).on_change(move |on, _, cx| set(&done, on, cx))),
|
.row("Published", Checkbox::new("know-published", now_done).on_change(move |on, _, cx| set(&done, on, cx))),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -104,8 +104,10 @@ impl RenderOnce for EpubReader {
|
|||||||
let shown = &self.chapters[chapter];
|
let shown = &self.chapters[chapter];
|
||||||
let listed = contents.then(|| {
|
let listed = contents.then(|| {
|
||||||
div()
|
div()
|
||||||
|
.id((self.id.clone(), "chapters"))
|
||||||
.flex_none()
|
.flex_none()
|
||||||
.w(theme.sidebar_width(false))
|
.w(theme.sidebar_width(false))
|
||||||
|
.overflow_y_scroll()
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
.gap_0p5()
|
.gap_0p5()
|
||||||
|
|||||||
@@ -10,12 +10,11 @@ pub(crate) struct Find {
|
|||||||
pub current: Option<usize>,
|
pub current: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Scrolls `hit` a third of the way down the view; before the pages are laid out, its page to the top.
|
/// Scrolls `hit` a third of the way down the view; runs after the pages' prepaint, so their bounds are this frame's.
|
||||||
pub(crate) fn reveal(scroll: &ScrollHandle, hit: &PageHit, zoom: f32) {
|
pub(crate) fn reveal(scroll: &ScrollHandle, hit: &PageHit, zoom: f32) {
|
||||||
let Some(page) = scroll.bounds_for_item(hit.page) else {
|
let page = scroll
|
||||||
scroll.scroll_to_top_of_item(hit.page);
|
.bounds_for_item(hit.page)
|
||||||
return;
|
.expect("the hit's page is laid out");
|
||||||
};
|
|
||||||
let view = scroll.bounds();
|
let view = scroll.bounds();
|
||||||
let target = page.top() + Pixels::from(hit.bounds.origin.y * zoom);
|
let target = page.top() + Pixels::from(hit.bounds.origin.y * zoom);
|
||||||
let offset = scroll.offset();
|
let offset = scroll.offset();
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
use std::{collections::BTreeMap, rc::Rc};
|
use std::{collections::BTreeMap, rc::Rc};
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, App, Bounds, ElementId, FontWeight, InteractiveElement, IntoElement, ParentElement,
|
AnyElement, App, Bounds, ElementId, FocusHandle, FontWeight, InteractiveElement, IntoElement,
|
||||||
RenderOnce, ScrollHandle, SharedString, Styled, Window, canvas, div, fill, prelude::*, size,
|
ParentElement, RenderOnce, ScrollHandle, SharedString, Styled, Window, canvas, div, fill,
|
||||||
|
prelude::*, size,
|
||||||
};
|
};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
@@ -271,12 +272,35 @@ impl ParentElement for ZenMode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Zen's own focus, where focus was before zen took it, and whether zen was on last frame.
|
||||||
|
struct Quiet {
|
||||||
|
focus: FocusHandle,
|
||||||
|
previous: Option<FocusHandle>,
|
||||||
|
was_on: bool,
|
||||||
|
}
|
||||||
|
|
||||||
impl RenderOnce for ZenMode {
|
impl RenderOnce for ZenMode {
|
||||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let theme = cx.theme();
|
|
||||||
let (on, exit) = (self.on, self.on_exit);
|
let (on, exit) = (self.on, self.on_exit);
|
||||||
|
let quiet = window.use_keyed_state((self.id.clone(), "quiet"), cx, |_, cx| Quiet {
|
||||||
|
focus: cx.focus_handle(),
|
||||||
|
previous: None,
|
||||||
|
was_on: false,
|
||||||
|
});
|
||||||
|
let (focus, was_on) = (quiet.read(cx).focus.clone(), quiet.read(cx).was_on);
|
||||||
|
if on && !was_on && !focus.contains_focused(window, cx) {
|
||||||
|
log::info!("zen mode: takes focus, so Escape reaches it");
|
||||||
|
let previous = window.focused(cx);
|
||||||
|
window.focus(&focus);
|
||||||
|
quiet.update(cx, |quiet, _| quiet.previous = previous);
|
||||||
|
}
|
||||||
|
if on != was_on {
|
||||||
|
quiet.update(cx, |quiet, _| quiet.was_on = on);
|
||||||
|
}
|
||||||
|
let theme = cx.theme();
|
||||||
div()
|
div()
|
||||||
.id(self.id.clone())
|
.id(self.id.clone())
|
||||||
|
.track_focus(&focus)
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
.on_key_down(move |event, window, cx| {
|
.on_key_down(move |event, window, cx| {
|
||||||
@@ -286,6 +310,9 @@ impl RenderOnce for ZenMode {
|
|||||||
{
|
{
|
||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
log::info!("zen mode: leaves");
|
log::info!("zen mode: leaves");
|
||||||
|
if let Some(previous) = quiet.update(cx, |quiet, _| quiet.previous.take()) {
|
||||||
|
window.focus(&previous);
|
||||||
|
}
|
||||||
exit(window, cx);
|
exit(window, cx);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
+126
-16
@@ -1,23 +1,33 @@
|
|||||||
use std::{cell::RefCell, rc::Rc};
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AppContext as _, Bounds, Context, Entity, IntoElement, Modifiers, ParentElement, Pixels, Point,
|
AppContext as _, Bounds, Context, Entity, Focusable, IntoElement, Modifiers, ParentElement,
|
||||||
Render, ScrollDelta, ScrollWheelEvent, Styled, TestAppContext, TouchPhase, VisualTestContext,
|
Pixels, Point, Render, ScrollDelta, ScrollWheelEvent, Styled, TestAppContext, TouchPhase,
|
||||||
Window, div, point, px, size,
|
VisualTestContext, Window, div, point, px, size,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{DocPage, DocumentViewer, PageHit};
|
use super::{DocPage, DocumentViewer, PageHit, ZenMode};
|
||||||
use crate::{
|
use crate::{
|
||||||
editor::FindWidget,
|
editor::FindWidget,
|
||||||
forms::TextInput,
|
forms::{Input, TextInput},
|
||||||
theme::{ActiveTheme, ControlSize, Theme},
|
theme::{ActiveTheme, ControlSize, Theme},
|
||||||
};
|
};
|
||||||
|
|
||||||
type Noted = Rc<RefCell<Option<(usize, Point<f32>)>>>;
|
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 {
|
struct Reader {
|
||||||
query: Entity<TextInput>,
|
query: Entity<TextInput>,
|
||||||
|
width: f32,
|
||||||
|
hits: Vec<PageHit>,
|
||||||
current: Option<usize>,
|
current: Option<usize>,
|
||||||
noted: Noted,
|
noted: Noted,
|
||||||
}
|
}
|
||||||
@@ -27,20 +37,16 @@ impl Render for Reader {
|
|||||||
let pages: Vec<DocPage> = (0..3)
|
let pages: Vec<DocPage> = (0..3)
|
||||||
.map(|ix| DocPage {
|
.map(|ix| DocPage {
|
||||||
source: format!("page-{ix}.png").into(),
|
source: format!("page-{ix}.png").into(),
|
||||||
width: 400.0,
|
width: self.width,
|
||||||
height: 1000.0,
|
height: 1000.0,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let hit = PageHit {
|
|
||||||
page: 1,
|
|
||||||
bounds: Bounds::new(point(40.0, 400.0), size(60.0, 12.0)),
|
|
||||||
};
|
|
||||||
let noted = self.noted.clone();
|
let noted = self.noted.clone();
|
||||||
div().size_full().child(
|
div().size_full().child(
|
||||||
DocumentViewer::new("viewer", "lift.pdf", pages)
|
DocumentViewer::new("viewer", "lift.pdf", pages)
|
||||||
.find(
|
.find(
|
||||||
FindWidget::new("find", &self.query, self.current, 1),
|
FindWidget::new("find", &self.query, self.current, self.hits.len()),
|
||||||
vec![hit],
|
self.hits.clone(),
|
||||||
self.current,
|
self.current,
|
||||||
)
|
)
|
||||||
.notes(Vec::new(), move |page, at, _, _| {
|
.notes(Vec::new(), move |page, at, _, _| {
|
||||||
@@ -51,12 +57,23 @@ impl Render for Reader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn open(cx: &mut TestAppContext) -> (Entity<Reader>, Noted, &mut VisualTestContext) {
|
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);
|
cx.update(Theme::init);
|
||||||
let noted = Noted::default();
|
let noted = Noted::default();
|
||||||
let seen = noted.clone();
|
let seen = noted.clone();
|
||||||
let (reader, cx) = cx.add_window_view(move |window, cx| Reader {
|
let (reader, cx) = cx.add_window_view(move |window, cx| Reader {
|
||||||
query: cx.new(|cx| TextInput::new(window, cx)),
|
query: cx.new(|cx| TextInput::new(window, cx)),
|
||||||
current: None,
|
width,
|
||||||
|
hits: vec![hit(1)],
|
||||||
|
current,
|
||||||
noted: seen,
|
noted: seen,
|
||||||
});
|
});
|
||||||
settle(cx);
|
settle(cx);
|
||||||
@@ -85,8 +102,13 @@ fn wheel(down: f32, cx: &mut VisualTestContext) {
|
|||||||
settle(cx);
|
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>) {
|
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 toggle = cx.update(|window, cx| {
|
||||||
let header = cx
|
let header = cx
|
||||||
.theme()
|
.theme()
|
||||||
@@ -99,7 +121,6 @@ fn pin(cx: &mut VisualTestContext, noted: &Noted) -> (usize, Point<f32>) {
|
|||||||
});
|
});
|
||||||
cx.simulate_click(toggle, Modifiers::none());
|
cx.simulate_click(toggle, Modifiers::none());
|
||||||
settle(cx);
|
settle(cx);
|
||||||
let at = on_pages(cx);
|
|
||||||
cx.simulate_click(at, Modifiers::none());
|
cx.simulate_click(at, Modifiers::none());
|
||||||
settle(cx);
|
settle(cx);
|
||||||
noted
|
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.0, 1, "the hit's page comes into view");
|
||||||
assert_eq!(from_top, from_below);
|
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, ¬ed);
|
||||||
|
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, ¬ed).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, ¬ed);
|
||||||
|
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");
|
||||||
|
}
|
||||||
+17
-14
@@ -146,6 +146,10 @@ impl DocumentViewer {
|
|||||||
"hit {current:?} of {}",
|
"hit {current:?} of {}",
|
||||||
hits.len()
|
hits.len()
|
||||||
);
|
);
|
||||||
|
assert!(
|
||||||
|
hits.iter().all(|hit| hit.page < self.pages.len()),
|
||||||
|
"a hit lies past the last page"
|
||||||
|
);
|
||||||
self.find = Some(Find {
|
self.find = Some(Find {
|
||||||
widget,
|
widget,
|
||||||
hits,
|
hits,
|
||||||
@@ -173,7 +177,7 @@ struct Desk {
|
|||||||
page: usize,
|
page: usize,
|
||||||
width: f32,
|
width: f32,
|
||||||
pinning: bool,
|
pinning: bool,
|
||||||
shown_hit: Option<usize>,
|
shown_hit: Option<PageHit>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderOnce for DocumentViewer {
|
impl RenderOnce for DocumentViewer {
|
||||||
@@ -184,18 +188,6 @@ impl RenderOnce for DocumentViewer {
|
|||||||
(desk.scroll.clone(), desk.page, desk.width, desk.pinning)
|
(desk.scroll.clone(), desk.page, desk.width, desk.pinning)
|
||||||
};
|
};
|
||||||
let zoom = self.zoom;
|
let zoom = self.zoom;
|
||||||
if let Some(Find {
|
|
||||||
hits,
|
|
||||||
current: Some(current),
|
|
||||||
..
|
|
||||||
}) = &self.find
|
|
||||||
&& desk.read(cx).shown_hit != Some(*current)
|
|
||||||
{
|
|
||||||
let hit = &hits[*current];
|
|
||||||
log::info!("document viewer: hit {current} on page {}", hit.page);
|
|
||||||
reveal(&scroll, hit, zoom);
|
|
||||||
desk.update(cx, |desk, _| desk.shown_hit = Some(*current));
|
|
||||||
}
|
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
let colors = theme.colors.clone();
|
let colors = theme.colors.clone();
|
||||||
let count = self.pages.len();
|
let count = self.pages.len();
|
||||||
@@ -294,12 +286,16 @@ impl RenderOnce for DocumentViewer {
|
|||||||
}) => (Some(widget), hits, current),
|
}) => (Some(widget), hits, current),
|
||||||
None => (None, Vec::new(), None),
|
None => (None, Vec::new(), None),
|
||||||
};
|
};
|
||||||
|
let target = current.map(|current| hits[current].clone());
|
||||||
let pages = self.pages.iter().enumerate().map(|(ix, page)| {
|
let pages = self.pages.iter().enumerate().map(|(ix, page)| {
|
||||||
let (on_note, scroll) = (self.on_note.clone().filter(|_| pinning), scroll.clone());
|
let (on_note, scroll) = (self.on_note.clone().filter(|_| pinning), scroll.clone());
|
||||||
div()
|
div()
|
||||||
.id((self.id.clone(), format!("page-{ix}")))
|
.id((self.id.clone(), format!("page-{ix}")))
|
||||||
.relative()
|
.relative()
|
||||||
.flex_none()
|
.flex_none()
|
||||||
|
.when(width == 0.0 || page.width * zoom <= width, |page| {
|
||||||
|
page.mx_auto()
|
||||||
|
})
|
||||||
.w(Pixels::from(page.width * zoom))
|
.w(Pixels::from(page.width * zoom))
|
||||||
.h(Pixels::from(page.height * zoom))
|
.h(Pixels::from(page.height * zoom))
|
||||||
.bg(colors.paper)
|
.bg(colors.paper)
|
||||||
@@ -406,7 +402,6 @@ impl RenderOnce for DocumentViewer {
|
|||||||
.track_scroll(&scroll)
|
.track_scroll(&scroll)
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
.items_center()
|
|
||||||
.gap_4()
|
.gap_4()
|
||||||
.p_6()
|
.p_6()
|
||||||
.children(pages),
|
.children(pages),
|
||||||
@@ -430,6 +425,14 @@ impl RenderOnce for DocumentViewer {
|
|||||||
});
|
});
|
||||||
window.request_animation_frame();
|
window.request_animation_frame();
|
||||||
}
|
}
|
||||||
|
if tracked.read(cx).shown_hit != target {
|
||||||
|
if let Some(hit) = &target {
|
||||||
|
log::info!("document viewer: hit on page {}", hit.page);
|
||||||
|
reveal(&tracked.read(cx).scroll, hit, zoom);
|
||||||
|
window.request_animation_frame();
|
||||||
|
}
|
||||||
|
measured.update(cx, |desk, _| desk.shown_hit = target.clone());
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|_, _, _, _| {},
|
|_, _, _, _| {},
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in new issue
Block a user