R8.1: document content keeps the file's colours in a dark app
Pages, cells, slides and PDF pages paint as the file says in any appearance; only the chrome around them follows dark mode. The kernel keeps Ely's one light scheme, sent once a view, and MuPDF no longer inverts. Reviews now run gpt-6.1-sol at xhigh. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
42 files changed
+204
-209
No files matched your search
@@ -36,7 +36,7 @@ fn fraction(key: &TileKey, size: (f32, f32)) -> [f32; 4] {
|
||||
|
||||
impl PdfView {
|
||||
/// The pages in view and the tiles each needs.
|
||||
pub fn showing(&self, sf: f32, dark: bool) -> Vec<Showing> {
|
||||
pub fn showing(&self, sf: f32) -> Vec<Showing> {
|
||||
let laid = self.laid_out();
|
||||
let (room_w, room_h) = (f32::from(self.room.size.width), f32::from(self.room.size.height));
|
||||
let (left, top) = (-f32::from(self.scroll.offset().x), -f32::from(self.scroll.offset().y));
|
||||
@@ -50,7 +50,7 @@ impl PdfView {
|
||||
let keys = ys
|
||||
.flat_map(|ty| xs.clone().map(move |tx| (tx, ty)))
|
||||
.map(|(tx, ty)| {
|
||||
let key = TileKey { page: place.page, scale, rotate: self.rotate, dark, tx, ty };
|
||||
let key = TileKey { page: place.page, scale, rotate: self.rotate, tx, ty };
|
||||
(key, [tx * TILE, ty * TILE, TILE.min(dw - tx * TILE), TILE.min(dh - ty * TILE)])
|
||||
})
|
||||
.collect();
|
||||
@@ -123,7 +123,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn a_tile_keeps_its_place_as_a_fraction_of_its_page() {
|
||||
let key = TileKey { page: 0, scale: 2000, rotate: 0, dark: false, tx: 1, ty: 0 };
|
||||
let key = TileKey { page: 0, scale: 2000, rotate: 0, tx: 1, ty: 0 };
|
||||
// A 612 by 792 page at 2x: 1224 by 1584.
|
||||
assert_eq!(fraction(&key, (612.0, 792.0)), [512.0 / 1224.0, 0.0, 512.0 / 1224.0, 512.0 / 1584.0]);
|
||||
let turned = TileKey { rotate: 90, ..key };
|
||||
|
||||
@@ -16,14 +16,13 @@ use crate::view::{PdfView, Reported};
|
||||
/// A tile's side in device pixels.
|
||||
pub const TILE: i32 = 512;
|
||||
|
||||
/// A page tile at one scale, turn and theme.
|
||||
/// A page tile at one scale and turn.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct TileKey {
|
||||
pub page: usize,
|
||||
/// Device pixels a point, times a thousand.
|
||||
pub scale: u32,
|
||||
pub rotate: i32,
|
||||
pub dark: bool,
|
||||
pub tx: i32,
|
||||
pub ty: i32,
|
||||
}
|
||||
@@ -123,17 +122,14 @@ impl PdfView {
|
||||
|
||||
/// Pages in view and their tiles; missing ones asked.
|
||||
pub fn page_view(&mut self, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
|
||||
let (sf, dark) = (window.scale_factor(), cx.theme().is_dark());
|
||||
let (sunken, paper) = {
|
||||
let colors = &cx.theme().colors;
|
||||
(colors.sunken, if dark { colors.surface } else { colors.paper })
|
||||
};
|
||||
let sf = window.scale_factor();
|
||||
let (sunken, paper) = (cx.theme().colors.sunken, gpui::white());
|
||||
let laid = self.laid_out();
|
||||
if let Some((x, y)) = self.go_to.take() {
|
||||
self.scroll.set_offset(point(px(-x), px(-y)));
|
||||
}
|
||||
let top = -f32::from(self.scroll.offset().y);
|
||||
let showing = self.showing(sf, dark);
|
||||
let showing = self.showing(sf);
|
||||
// The current page: first showing more than a sliver.
|
||||
if let Some(first) = showing.iter().map(|s| s.place).find(|p| p.y + p.h > top + GAP * 3.0) {
|
||||
let paired = showing.iter().any(|s| s.place.page == self.current) && first.page / 2 == self.current / 2;
|
||||
@@ -266,7 +262,7 @@ impl PdfView {
|
||||
self.painting = true;
|
||||
let (doc, scale, version) = (self.doc, key.scale as f32 / 1000.0, self.version);
|
||||
self.off(
|
||||
move |worker| worker.render(doc, key.page, (scale, key.rotate), rect, key.dark),
|
||||
move |worker| worker.render(doc, key.page, (scale, key.rotate), rect),
|
||||
move |view, painted, cx| {
|
||||
view.painting = false;
|
||||
if view.version != version {
|
||||
|
||||
+13
-13
@@ -100,11 +100,11 @@ impl PdfView {
|
||||
}
|
||||
|
||||
/// Asks for the first missing picture in `rows`.
|
||||
fn ask_thumb(&mut self, rows: Range<usize>, (sf, dark): (f32, bool), cx: &mut Context<Self>) {
|
||||
fn ask_thumb(&mut self, rows: Range<usize>, sf: f32, cx: &mut Context<Self>) {
|
||||
if self.thumbing {
|
||||
return;
|
||||
}
|
||||
let due = |page: &usize| !self.thumbs.contains_key(&(*page, dark)) || self.thumbs_stale.contains(&(*page, dark));
|
||||
let due = |page: &usize| !self.thumbs.contains_key(page) || self.thumbs_stale.contains(page);
|
||||
let Some(page) = rows.clone().find(|page| due(page) && !self.thumbs_failed.contains(page)) else {
|
||||
return;
|
||||
};
|
||||
@@ -114,7 +114,7 @@ impl PdfView {
|
||||
let (doc, version) = (self.doc, self.version);
|
||||
self.thumbing = true;
|
||||
self.off(
|
||||
move |worker| worker.render(doc, page, (scale, 0), tile, dark),
|
||||
move |worker| worker.render(doc, page, (scale, 0), tile),
|
||||
move |view, painted, cx| {
|
||||
view.thumbing = false;
|
||||
if view.version != version {
|
||||
@@ -128,8 +128,8 @@ impl PdfView {
|
||||
});
|
||||
match painted.map(|bgra| crate::pages::picture((tile[2] as u32, tile[3] as u32), bgra)) {
|
||||
Ok(Some(image)) => {
|
||||
view.thumbs_stale.remove(&(page, dark));
|
||||
if let Some(old) = view.thumbs.insert((page, dark), image) {
|
||||
view.thumbs_stale.remove(&page);
|
||||
if let Some(old) = view.thumbs.insert(page, image) {
|
||||
view.retired.push(old);
|
||||
}
|
||||
}
|
||||
@@ -149,18 +149,18 @@ impl PdfView {
|
||||
|
||||
impl PdfView {
|
||||
/// The picture a row draws; an old one until repainted.
|
||||
pub fn picture(&self, page: usize, dark: bool) -> Option<Arc<RenderImage>> {
|
||||
self.thumbs.get(&(page, dark)).cloned()
|
||||
pub fn picture(&self, page: usize) -> Option<Arc<RenderImage>> {
|
||||
self.thumbs.get(&page).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
/// One row: the page number and its picture.
|
||||
fn row(view: &Entity<PdfView>, page: usize, dark: bool, cx: &App) -> gpui::AnyElement {
|
||||
fn row(view: &Entity<PdfView>, page: usize, cx: &App) -> gpui::AnyElement {
|
||||
let read = view.read(cx);
|
||||
let colors = &cx.theme().colors;
|
||||
let (w, h) = read.sizes[page];
|
||||
let picked = read.picked.contains(&page);
|
||||
let picture = read.picture(page, dark);
|
||||
let picture = read.picture(page);
|
||||
let count = read.sizes.len();
|
||||
let label = Viewer::text(cx, "pdf.page.label", &[("n", &(page + 1).to_string())]);
|
||||
let (clicked, pressed, dropped) = (view.clone(), view.clone(), view.clone());
|
||||
@@ -210,7 +210,7 @@ fn row(view: &Entity<PdfView>, page: usize, dark: bool, cx: &App) -> gpui::AnyEl
|
||||
.h(px(height))
|
||||
.border_1()
|
||||
.border_color(if picked { colors.focus } else { colors.border })
|
||||
.bg(if dark { colors.surface } else { colors.paper })
|
||||
.bg(gpui::white())
|
||||
.when_some(picture, |frame, picture| frame.child(img(picture).size_full())),
|
||||
)
|
||||
.child(div().text_xs().text_color(colors.fg_subtle).child((page + 1).to_string()))
|
||||
@@ -258,12 +258,12 @@ impl Render for Side {
|
||||
Pane::Notes => crate::notelist::list(&view, cx),
|
||||
Pane::Files => crate::attach::list(&view, cx),
|
||||
Pane::Pages => {
|
||||
let shown = (window.scale_factor(), cx.theme().is_dark());
|
||||
let sf = window.scale_factor();
|
||||
let count = view.read(cx).sizes.len();
|
||||
let rows = view.clone();
|
||||
let list = uniform_list("pdf-thumbs", count, move |range, _, cx| {
|
||||
rows.update(cx, |view, cx| view.ask_thumb(range.clone(), shown, cx));
|
||||
range.map(|page| row(&rows, page, shown.1, cx)).collect()
|
||||
rows.update(cx, |view, cx| view.ask_thumb(range.clone(), sf, cx));
|
||||
range.map(|page| row(&rows, page, cx)).collect()
|
||||
})
|
||||
.size_full();
|
||||
div()
|
||||
|
||||
@@ -188,13 +188,13 @@ fn tiles_beside_the_view_are_neither_asked_nor_kept() {
|
||||
|
||||
/// Flags any notify leaving a shown page with nothing drawn.
|
||||
fn never_bare(cx: &mut VisualTestContext, view: &Entity<PdfView>) -> (gpui::Subscription, std::rc::Rc<std::cell::Cell<bool>>) {
|
||||
let (sf, dark) = cx.update(|window, app| (window.scale_factor(), ely_gpui_component::theme::ActiveTheme::theme(app).is_dark()));
|
||||
let sf = cx.update(|window, _| window.scale_factor());
|
||||
let bare = std::rc::Rc::new(std::cell::Cell::new(false));
|
||||
let seen = bare.clone();
|
||||
let watch = cx.update(|_, app| {
|
||||
app.observe(view, move |view, app| {
|
||||
let read = view.read(app);
|
||||
if read.showing(sf, dark).iter().any(|page| read.pieces(page).is_empty()) {
|
||||
if read.showing(sf).iter().any(|page| read.pieces(page).is_empty()) {
|
||||
seen.set(true);
|
||||
}
|
||||
})
|
||||
@@ -248,10 +248,10 @@ fn a_new_scale_keeps_the_old_tiles_until_new_ones_land() {
|
||||
cx.simulate_resize(gpui::size(px(700.), px(800.)));
|
||||
// The new size lays out before any new tile lands.
|
||||
cx.run_until_parked();
|
||||
let (sf, dark) = cx.update(|window, app| (window.scale_factor(), ely_gpui_component::theme::ActiveTheme::theme(app).is_dark()));
|
||||
let sf = cx.update(|window, _| window.scale_factor());
|
||||
let now = cx.update(|_, app| {
|
||||
let read = view.read(app);
|
||||
read.showing(sf, dark).iter().map(|page| read.pieces(page).len()).collect::<Vec<_>>()
|
||||
read.showing(sf).iter().map(|page| read.pieces(page).len()).collect::<Vec<_>>()
|
||||
});
|
||||
assert!(now.iter().all(|count| *count > 0), "drawn right after the resize: {now:?}");
|
||||
settle(cx, |app| view.read(app).laid_out().scale != scale && repainted(view.read(app)));
|
||||
@@ -315,7 +315,7 @@ fn old_tiles_are_painted_and_freed_with_their_tab() {
|
||||
let shown = cx.update(|window, app| {
|
||||
let read = view.read(app);
|
||||
let room = read.room;
|
||||
read.showing(window.scale_factor(), false).iter().map(|s| {
|
||||
read.showing(window.scale_factor()).iter().map(|s| {
|
||||
let (left, top) = (-f32::from(read.scroll.offset().x), -f32::from(read.scroll.offset().y));
|
||||
let w = (s.place.x + s.place.w).min(left + f32::from(room.size.width)) - s.place.x.max(left);
|
||||
let h = (s.place.y + s.place.h).min(top + f32::from(room.size.height)) - s.place.y.max(top);
|
||||
@@ -333,9 +333,8 @@ fn an_edit_keeps_page_pictures_until_they_repaint() {
|
||||
let mut cx = TestAppContext::single();
|
||||
let (viewer, cx) = windowed(&mut cx, dir.path(), &[file(dir.path(), "one.pdf", &[(612.0, 792.0, &["Hello"])])], true);
|
||||
let view = shown(cx, &viewer);
|
||||
let dark = cx.update(|_, app| ely_gpui_component::theme::ActiveTheme::theme(app).is_dark());
|
||||
settle(cx, |app| view.read(app).picture(0, dark).is_some() && repainted(view.read(app)));
|
||||
let old = cx.update(|_, app| view.read(app).picture(0, dark).expect("a picture"));
|
||||
settle(cx, |app| view.read(app).picture(0).is_some() && repainted(view.read(app)));
|
||||
let old = cx.update(|_, app| view.read(app).picture(0).expect("a picture"));
|
||||
// A picture on its way holds back the repaint.
|
||||
cx.update(|_, app| view.update(app, |view, _| view.thumbing = true));
|
||||
cx.update(|_, app| view.update(app, |view, cx| view.annotate("adding a note", vec![note()], cx)));
|
||||
@@ -349,7 +348,7 @@ fn an_edit_keeps_page_pictures_until_they_repaint() {
|
||||
cx.notify()
|
||||
}));
|
||||
settle(cx, |app| view.read(app).thumbs_stale.is_empty());
|
||||
let new = cx.update(|_, app| view.read(app).picture(0, dark).expect("a picture"));
|
||||
let new = cx.update(|_, app| view.read(app).picture(0).expect("a picture"));
|
||||
assert!(!std::sync::Arc::ptr_eq(&new, &old), "the new picture replaces it");
|
||||
super::frames(cx);
|
||||
assert!(cx.update(|window, _| window.has_image_atlas_entry(&new)), "and is drawn");
|
||||
|
||||
@@ -158,7 +158,7 @@ fn scanned(dir: &std::path::Path) -> std::path::PathBuf {
|
||||
let doc = worker.open(&source).expect("opens").doc;
|
||||
let s: f32 = 200.0 / 72.0;
|
||||
let (w, h) = ((612.0 * s).ceil() as i32, (792.0 * s).ceil() as i32);
|
||||
let bgra = worker.render(doc, 0, (s, 0), [0, 0, w, h], false).expect("draws").bgra;
|
||||
let bgra = worker.render(doc, 0, (s, 0), [0, 0, w, h]).expect("draws").bgra;
|
||||
let gray: Vec<u8> = bgra.chunks_exact(4).map(|p| ((u16::from(p[0]) + u16::from(p[1]) + u16::from(p[2])) / 3) as u8).collect();
|
||||
let path = dir.join("scan.pdf");
|
||||
std::fs::write(&path, scan((612.0, 792.0), (w as u32, h as u32), &gray)).expect("writes");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Password prompts, zoomed pairs, jumps across (C11.1).
|
||||
|
||||
use ely_office::{Dialog, Dialogs as _, Tabs as _};
|
||||
use ely_office::{Dialog, Dialogs as _, Opening as _, Tabs as _};
|
||||
use gpui::{Entity, Modifiers, ScrollDelta, ScrollWheelEvent, TestAppContext, VisualTestContext, point, px};
|
||||
|
||||
use super::{file, frames, settle, shown, windowed};
|
||||
@@ -144,3 +144,21 @@ fn ctrl_and_a_wheel_notch_step_the_zoom_once() {
|
||||
frames(cx);
|
||||
assert_eq!(read(cx).0, Zoom::Scale(crate::keys::stepped(1.0, false)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_dark_app_shows_the_page_as_the_file_paints_it() {
|
||||
use ely_gpui_component::theme::{Mode, Theme};
|
||||
let dir = tempfile::tempdir().expect("a temp dir");
|
||||
let mut cx = TestAppContext::single();
|
||||
let (viewer, cx) = windowed(&mut cx, dir.path(), &[], true);
|
||||
cx.update(|_, app| Theme::set_mode_now(Mode::Dark, app));
|
||||
let path = file(dir.path(), "one.pdf", &[(612.0, 792.0, &["alpha"])]);
|
||||
cx.update(|_, app| viewer.update(app, |viewer, cx| viewer.open_file(path, cx)));
|
||||
settle(cx, |app| viewer.read(app).active_view().is_some());
|
||||
let view = shown(cx, &viewer);
|
||||
let corner = |app: &gpui::App| view.read(app).tiles.iter().find(|(key, _)| key.tx == 0 && key.ty == 0).map(|(_, image)| image.clone());
|
||||
settle(cx, |app| corner(app).is_some());
|
||||
let tile = cx.update(|_, app| corner(app)).expect("the first tile");
|
||||
let bytes = tile.as_bytes(0).expect("its pixels");
|
||||
assert_eq!(&bytes[..3], &[255, 255, 255], "the paper stays white");
|
||||
}
|
||||
@@ -87,11 +87,11 @@ pub struct PdfView {
|
||||
pub saved: String,
|
||||
/// Saves so far: a read from before one is stale.
|
||||
pub saves: u64,
|
||||
/// Page pictures for the side panel, by page and darkness.
|
||||
pub thumbs: HashMap<(usize, bool), Arc<RenderImage>>,
|
||||
/// Page pictures for the side panel, by page.
|
||||
pub thumbs: HashMap<usize, Arc<RenderImage>>,
|
||||
pub thumbs_failed: HashSet<usize>,
|
||||
/// Pictures from before an edit; shown until repainted.
|
||||
pub thumbs_stale: HashSet<(usize, bool)>,
|
||||
pub thumbs_stale: HashSet<usize>,
|
||||
/// A picture is on its way from the worker.
|
||||
pub thumbing: bool,
|
||||
/// Pages picked in the side panel, and the anchor.
|
||||
|
||||
Reference in new issue
Block a user