summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/periodic.c1
-rw-r--r--src/ui/documentwidget.c1431
2 files changed, 698 insertions, 734 deletions
diff --git a/src/periodic.c b/src/periodic.c
index ef3d8033..c65c8b07 100644
--- a/src/periodic.c
+++ b/src/periodic.c
@@ -121,6 +121,7 @@ void deinit_Periodic(iPeriodic *d) {
121} 121}
122 122
123void add_Periodic(iPeriodic *d, iAny *context, const char *command) { 123void add_Periodic(iPeriodic *d, iAny *context, const char *command) {
124 iAssert(isInstance_Object(context, &Class_Widget));
124 lock_Mutex(d->mutex); 125 lock_Mutex(d->mutex);
125 size_t pos; 126 size_t pos;
126 iPeriodicCommand key = { .context = context }; 127 iPeriodicCommand key = { .context = context };
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index fa035d86..f5b9a4fc 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -251,6 +251,35 @@ enum iWheelSwipeState {
251 direct_WheelSwipeState, 251 direct_WheelSwipeState,
252}; 252};
253 253
254/* TODO: DocumentView is supposed to be useful on its own; move to a separate source file. */
255iDeclareType(DocumentView)
256
257struct Impl_DocumentView {
258 iDocumentWidget *owner; /* TODO: Convert to an abstract provider of metrics? */
259 iGmDocument * doc;
260 int pageMargin;
261 iSmoothScroll scrollY;
262 iAnim sideOpacity;
263 iAnim altTextOpacity;
264 iGmRunRange visibleRuns;
265 iPtrArray visibleLinks;
266 iPtrArray visiblePre;
267 iPtrArray visibleMedia; /* currently playing audio / ongoing downloads */
268 iPtrArray visibleWideRuns; /* scrollable blocks; TODO: merge into `visiblePre` */
269 const iGmRun * hoverPre; /* for clicking */
270 const iGmRun * hoverAltPre; /* for drawing alt text */
271 const iGmRun * hoverLink;
272 iArray wideRunOffsets;
273 iAnim animWideRunOffset;
274 uint16_t animWideRunId;
275 iGmRunRange animWideRunRange;
276 iDrawBufs * drawBufs; /* dynamic state for drawing */
277 iVisBuf * visBuf;
278 iVisBufMeta * visBufMeta;
279 iGmRunRange renderRuns;
280 iPtrSet * invalidRuns;
281};
282
254struct Impl_DocumentWidget { 283struct Impl_DocumentWidget {
255 iWidget widget; 284 iWidget widget;
256 int flags; /* internal behavior, see enum iDocumentWidgetFlag */ 285 int flags; /* internal behavior, see enum iDocumentWidgetFlag */
@@ -264,9 +293,6 @@ struct Impl_DocumentWidget {
264 const iGmRun * grabbedPlayer; /* currently adjusting volume in a player */ 293 const iGmRun * grabbedPlayer; /* currently adjusting volume in a player */
265 float grabbedStartVolume; 294 float grabbedStartVolume;
266 int mediaTimer; 295 int mediaTimer;
267 const iGmRun * hoverPre; /* for clicking */
268 const iGmRun * hoverAltPre; /* for drawing alt text */
269 const iGmRun * hoverLink;
270 const iGmRun * contextLink; 296 const iGmRun * contextLink;
271 iClick click; 297 iClick click;
272 iInt2 contextPos; /* coordinates of latest right click */ 298 iInt2 contextPos; /* coordinates of latest right click */
@@ -299,29 +325,11 @@ struct Impl_DocumentWidget {
299 iBlock sourceContent; /* original content as received, for saving; set on request finish */ 325 iBlock sourceContent; /* original content as received, for saving; set on request finish */
300 iTime sourceTime; 326 iTime sourceTime;
301 iGempub * sourceGempub; /* NULL unless the page is Gempub content */ 327 iGempub * sourceGempub; /* NULL unless the page is Gempub content */
302 iGmDocument * doc;
303 iBanner * banner; 328 iBanner * banner;
329 float initNormScrollY;
304 330
305 /* Rendering: */ 331 /* Rendering: */
306 int pageMargin; 332 iDocumentView view;
307 float initNormScrollY;
308 iSmoothScroll scrollY;
309 iAnim sideOpacity;
310 iAnim altTextOpacity;
311 iGmRunRange visibleRuns;
312 iPtrArray visibleLinks;
313 iPtrArray visiblePre;
314 iPtrArray visibleMedia; /* currently playing audio / ongoing downloads */
315 iPtrArray visibleWideRuns; /* scrollable blocks; TODO: merge into `visiblePre` */
316 iArray wideRunOffsets;
317 iAnim animWideRunOffset;
318 uint16_t animWideRunId;
319 iGmRunRange animWideRunRange;
320 iDrawBufs * drawBufs; /* dynamic state for drawing */
321 iVisBuf * visBuf;
322 iVisBufMeta * visBufMeta;
323 iGmRunRange renderRuns;
324 iPtrSet * invalidRuns;
325 iLinkInfo * linkInfo; 333 iLinkInfo * linkInfo;
326 334
327 /* Widget structure: */ 335 /* Widget structure: */
@@ -338,6 +346,44 @@ iDefineObjectConstruction(DocumentWidget)
338 346
339static int docEnum_ = 0; 347static int docEnum_ = 0;
340 348
349void init_DocumentView(iDocumentView *d) {
350 d->owner = NULL;
351 d->doc = new_GmDocument();
352 d->invalidRuns = new_PtrSet();
353 d->drawBufs = new_DrawBufs();
354 d->pageMargin = 5;
355 if (deviceType_App() != desktop_AppDeviceType) {
356 d->scrollY.flags |= pullDownAction_SmoothScrollFlag; /* pull to refresh */
357 }
358 d->hoverPre = NULL;
359 d->hoverAltPre = NULL;
360 d->hoverLink = NULL;
361 d->animWideRunId = 0;
362 init_Anim(&d->animWideRunOffset, 0);
363 iZap(d->renderRuns);
364 iZap(d->visibleRuns);
365 d->visBuf = new_VisBuf(); {
366 d->visBufMeta = malloc(sizeof(iVisBufMeta) * numBuffers_VisBuf);
367 /* Additional metadata for each buffer. */
368 d->visBuf->bufferInvalidated = visBufInvalidated_;
369 for (size_t i = 0; i < numBuffers_VisBuf; i++) {
370 d->visBuf->buffers[i].user = d->visBufMeta + i;
371 }
372 }
373 init_Anim(&d->sideOpacity, 0);
374 init_Anim(&d->altTextOpacity, 0);
375 init_PtrArray(&d->visibleLinks);
376 init_PtrArray(&d->visiblePre);
377 init_PtrArray(&d->visibleWideRuns);
378 init_Array(&d->wideRunOffsets, sizeof(int));
379 init_PtrArray(&d->visibleMedia);
380}
381
382static void setOwner_DocumentView_(iDocumentView *d, iDocumentWidget *doc) {
383 d->owner = doc;
384 init_SmoothScroll(&d->scrollY, as_Widget(doc), scrollBegan_DocumentWidget_);
385}
386
341void init_DocumentWidget(iDocumentWidget *d) { 387void init_DocumentWidget(iDocumentWidget *d) {
342 iWidget *w = as_Widget(d); 388 iWidget *w = as_Widget(d);
343 init_Widget(w); 389 init_Widget(w);
@@ -365,61 +411,33 @@ void init_DocumentWidget(iDocumentWidget *d) {
365 d->request = NULL; 411 d->request = NULL;
366 d->isRequestUpdated = iFalse; 412 d->isRequestUpdated = iFalse;
367 d->media = new_ObjectList(); 413 d->media = new_ObjectList();
368 d->doc = new_GmDocument();
369 d->banner = new_Banner(); 414 d->banner = new_Banner();
370 setOwner_Banner(d->banner, d); 415 setOwner_Banner(d->banner, d);
371 d->redirectCount = 0; 416 d->redirectCount = 0;
372 d->ordinalBase = 0; 417 d->ordinalBase = 0;
373 d->initNormScrollY = 0;
374 init_SmoothScroll(&d->scrollY, w, scrollBegan_DocumentWidget_);
375 if (deviceType_App() != desktop_AppDeviceType) {
376 d->scrollY.flags |= pullDownAction_SmoothScrollFlag; /* pull to refresh */
377 }
378 d->animWideRunId = 0;
379 init_Anim(&d->animWideRunOffset, 0);
380 d->wheelSwipeState = none_WheelSwipeState; 418 d->wheelSwipeState = none_WheelSwipeState;
381 d->selectMark = iNullRange; 419 d->selectMark = iNullRange;
382 d->foundMark = iNullRange; 420 d->foundMark = iNullRange;
383 d->pageMargin = 5;
384 d->hoverPre = NULL;
385 d->hoverAltPre = NULL;
386 d->hoverLink = NULL;
387 d->contextLink = NULL; 421 d->contextLink = NULL;
388 iZap(d->renderRuns);
389 iZap(d->visibleRuns);
390 d->visBuf = new_VisBuf(); {
391 d->visBufMeta = malloc(sizeof(iVisBufMeta) * numBuffers_VisBuf);
392 /* Additional metadata for each buffer. */
393 d->visBuf->bufferInvalidated = visBufInvalidated_;
394 for (size_t i = 0; i < numBuffers_VisBuf; i++) {
395 d->visBuf->buffers[i].user = d->visBufMeta + i;
396 }
397 }
398 d->invalidRuns = new_PtrSet();
399 init_Anim(&d->sideOpacity, 0);
400 init_Anim(&d->altTextOpacity, 0);
401 d->sourceStatus = none_GmStatusCode; 422 d->sourceStatus = none_GmStatusCode;
402 init_String(&d->sourceHeader); 423 init_String(&d->sourceHeader);
403 init_String(&d->sourceMime); 424 init_String(&d->sourceMime);
404 init_Block(&d->sourceContent, 0); 425 init_Block(&d->sourceContent, 0);
405 iZap(d->sourceTime); 426 iZap(d->sourceTime);
406 d->sourceGempub = NULL; 427 d->sourceGempub = NULL;
407 init_PtrArray(&d->visibleLinks); 428 d->initNormScrollY = 0;
408 init_PtrArray(&d->visiblePre); 429 d->grabbedPlayer = NULL;
409 init_PtrArray(&d->visibleWideRuns); 430 d->mediaTimer = 0;
410 init_Array(&d->wideRunOffsets, sizeof(int));
411 init_PtrArray(&d->visibleMedia);
412 d->grabbedPlayer = NULL;
413 d->mediaTimer = 0;
414 init_String(&d->pendingGotoHeading); 431 init_String(&d->pendingGotoHeading);
415 init_String(&d->linePrecedingLink); 432 init_String(&d->linePrecedingLink);
416 init_Click(&d->click, d, SDL_BUTTON_LEFT); 433 init_Click(&d->click, d, SDL_BUTTON_LEFT);
417 d->linkInfo = (deviceType_App() == desktop_AppDeviceType ? new_LinkInfo() : NULL); 434 d->linkInfo = (deviceType_App() == desktop_AppDeviceType ? new_LinkInfo() : NULL);
435 init_DocumentView(&d->view);
436 setOwner_DocumentView_(&d->view, d);
418 addChild_Widget(w, iClob(d->scroll = new_ScrollWidget())); 437 addChild_Widget(w, iClob(d->scroll = new_ScrollWidget()));
419 d->menu = NULL; /* created when clicking */ 438 d->menu = NULL; /* created when clicking */
420 d->playerMenu = NULL; 439 d->playerMenu = NULL;
421 d->copyMenu = NULL; 440 d->copyMenu = NULL;
422 d->drawBufs = new_DrawBufs();
423 d->translation = NULL; 441 d->translation = NULL;
424 addChildFlags_Widget(w, 442 addChildFlags_Widget(w,
425 iClob(new_IndicatorWidget()), 443 iClob(new_IndicatorWidget()),
@@ -443,22 +461,32 @@ void cancelAllRequests_DocumentWidget(iDocumentWidget *d) {
443 } 461 }
444 if (d->request) { 462 if (d->request) {
445 cancel_GmRequest(d->request); 463 cancel_GmRequest(d->request);
464}
446 } 465 }
466
467void deinit_DocumentView(iDocumentView *d) {
468 delete_DrawBufs(d->drawBufs);
469 delete_VisBuf(d->visBuf);
470 free(d->visBufMeta);
471 delete_PtrSet(d->invalidRuns);
472 deinit_Array(&d->wideRunOffsets);
473 deinit_PtrArray(&d->visibleMedia);
474 deinit_PtrArray(&d->visibleWideRuns);
475 deinit_PtrArray(&d->visiblePre);
476 deinit_PtrArray(&d->visibleLinks);
477 iReleasePtr(&d->doc);
447} 478}
448 479
449void deinit_DocumentWidget(iDocumentWidget *d) { 480void deinit_DocumentWidget(iDocumentWidget *d) {
450// printf("\n* * * * * * * *\nDEINIT DOCUMENT: %s\n* * * * * * * *\n\n", 481// printf("\n* * * * * * * *\nDEINIT DOCUMENT: %s\n* * * * * * * *\n\n",
451// cstr_String(&d->widget.id)); fflush(stdout); 482// cstr_String(&d->widget.id)); fflush(stdout);
452 cancelAllRequests_DocumentWidget(d); 483 cancelAllRequests_DocumentWidget(d);
453 pauseAllPlayers_Media(media_GmDocument(d->doc), iTrue); 484 pauseAllPlayers_Media(media_GmDocument(d->view.doc), iTrue);
454 removeTicker_App(animate_DocumentWidget_, d); 485 removeTicker_App(animate_DocumentWidget_, d);
455 removeTicker_App(prerender_DocumentWidget_, d); 486 removeTicker_App(prerender_DocumentWidget_, d);
456 remove_Periodic(periodic_App(), d); 487 remove_Periodic(periodic_App(), d);
457 delete_Translation(d->translation); 488 delete_Translation(d->translation);
458 delete_DrawBufs(d->drawBufs); 489 deinit_DocumentView(&d->view);
459 delete_VisBuf(d->visBuf);
460 free(d->visBufMeta);
461 delete_PtrSet(d->invalidRuns);
462 delete_LinkInfo(d->linkInfo); 490 delete_LinkInfo(d->linkInfo);
463 iRelease(d->media); 491 iRelease(d->media);
464 iRelease(d->request); 492 iRelease(d->request);
@@ -469,15 +497,9 @@ void deinit_DocumentWidget(iDocumentWidget *d) {
469 deinit_String(&d->sourceMime); 497 deinit_String(&d->sourceMime);
470 deinit_String(&d->sourceHeader); 498 deinit_String(&d->sourceHeader);
471 delete_Banner(d->banner); 499 delete_Banner(d->banner);
472 iRelease(d->doc);
473 if (d->mediaTimer) { 500 if (d->mediaTimer) {
474 SDL_RemoveTimer(d->mediaTimer); 501 SDL_RemoveTimer(d->mediaTimer);
475 } 502 }
476 deinit_Array(&d->wideRunOffsets);
477 deinit_PtrArray(&d->visibleMedia);
478 deinit_PtrArray(&d->visibleWideRuns);
479 deinit_PtrArray(&d->visiblePre);
480 deinit_PtrArray(&d->visibleLinks);
481 delete_Block(d->certFingerprint); 503 delete_Block(d->certFingerprint);
482 delete_String(d->certSubject); 504 delete_String(d->certSubject);
483 delete_String(d->titleUser); 505 delete_String(d->titleUser);
@@ -511,7 +533,7 @@ static void setLinkNumberMode_DocumentWidget_(iDocumentWidget *d, iBool set) {
511 } 533 }
512} 534}
513 535
514static void resetWideRuns_DocumentWidget_(iDocumentWidget *d) { 536static void resetWideRuns_DocumentView_(iDocumentView *d) {
515 clear_Array(&d->wideRunOffsets); 537 clear_Array(&d->wideRunOffsets);
516 d->animWideRunId = 0; 538 d->animWideRunId = 0;
517 init_Anim(&d->animWideRunOffset, 0); 539 init_Anim(&d->animWideRunOffset, 0);
@@ -539,8 +561,8 @@ static void requestFinished_DocumentWidget_(iAnyObject *obj) {
539 d->request); 561 d->request);
540} 562}
541 563
542static int documentWidth_DocumentWidget_(const iDocumentWidget *d) { 564static int documentWidth_DocumentView_(const iDocumentView *d) {
543 const iWidget *w = constAs_Widget(d); 565 const iWidget *w = constAs_Widget(d->owner);
544 const iRect bounds = bounds_Widget(w); 566 const iRect bounds = bounds_Widget(w);
545 const iPrefs * prefs = prefs_App(); 567 const iPrefs * prefs = prefs_App();
546 const int minWidth = 50 * gap_UI; /* lines must fit a word at least */ 568 const int minWidth = 50 * gap_UI; /* lines must fit a word at least */
@@ -552,18 +574,18 @@ static int documentWidth_DocumentWidget_(const iDocumentWidget *d) {
552 prefs->lineWidth * prefs->zoomPercent / 100); 574 prefs->lineWidth * prefs->zoomPercent / 100);
553} 575}
554 576
555static int documentTopPad_DocumentWidget_(const iDocumentWidget *d) { 577static int documentTopPad_DocumentView_(const iDocumentView *d) {
556 /* Amount of space between banner and top of the document. */ 578 /* Amount of space between banner and top of the document. */
557 return isEmpty_Banner(d->banner) ? 0 : lineHeight_Text(paragraph_FontId); 579 return isEmpty_Banner(d->owner->banner) ? 0 : lineHeight_Text(paragraph_FontId);
558} 580}
559 581
560static int documentTopMargin_DocumentWidget_(const iDocumentWidget *d) { 582static int documentTopMargin_DocumentView_(const iDocumentView *d) {
561 return (isEmpty_Banner(d->banner) ? d->pageMargin * gap_UI : height_Banner(d->banner)) + 583 return (isEmpty_Banner(d->owner->banner) ? d->pageMargin * gap_UI : height_Banner(d->owner->banner)) +
562 documentTopPad_DocumentWidget_(d); 584 documentTopPad_DocumentView_(d);
563} 585}
564 586
565static int pageHeight_DocumentWidget_(const iDocumentWidget *d) { 587static int pageHeight_DocumentView_(const iDocumentView *d) {
566 return height_Banner(d->banner) + documentTopPad_DocumentWidget_(d) + size_GmDocument(d->doc).y; 588 return height_Banner(d->owner->banner) + documentTopPad_DocumentView_(d) + size_GmDocument(d->doc).y;
567} 589}
568 590
569static int phoneToolbarHeight_DocumentWidget_(const iDocumentWidget *d) { 591static int phoneToolbarHeight_DocumentWidget_(const iDocumentWidget *d) {
@@ -582,39 +604,41 @@ static int footerHeight_DocumentWidget_(const iDocumentWidget *d) {
582 return hgt; 604 return hgt;
583} 605}
584 606
585static iRect documentBounds_DocumentWidget_(const iDocumentWidget *d) { 607static iRect documentBounds_DocumentView_(const iDocumentView *d) {
586 const iRect bounds = bounds_Widget(constAs_Widget(d)); 608 const iRect bounds = bounds_Widget(constAs_Widget(d->owner));
587 const int margin = gap_UI * d->pageMargin; 609 const int margin = gap_UI * d->pageMargin;
588 iRect rect; 610 iRect rect;
589 rect.size.x = documentWidth_DocumentWidget_(d); 611 rect.size.x = documentWidth_DocumentView_(d);
590 rect.pos.x = mid_Rect(bounds).x - rect.size.x / 2; 612 rect.pos.x = mid_Rect(bounds).x - rect.size.x / 2;
591 rect.pos.y = top_Rect(bounds) + margin; 613 rect.pos.y = top_Rect(bounds) + margin;
592 rect.size.y = height_Rect(bounds) - margin; 614 rect.size.y = height_Rect(bounds) - margin;
593 iBool wasCentered = iFalse; 615 iBool wasCentered = iFalse;
594 if (d->flags & centerVertically_DocumentWidgetFlag) { 616 /* TODO: Further separation of View and Widget: configure header and footer heights
617 without involving the widget here. */
618 if (d->owner->flags & centerVertically_DocumentWidgetFlag) {
595 const int docSize = size_GmDocument(d->doc).y + 619 const int docSize = size_GmDocument(d->doc).y +
596 documentTopMargin_DocumentWidget_(d); 620 documentTopMargin_DocumentView_(d);
597 if (size_GmDocument(d->doc).y == 0) { 621 if (size_GmDocument(d->doc).y == 0) {
598 /* Document is empty; maybe just showing an error banner. */ 622 /* Document is empty; maybe just showing an error banner. */
599 rect.pos.y = top_Rect(bounds) + height_Rect(bounds) / 2 - 623 rect.pos.y = top_Rect(bounds) + height_Rect(bounds) / 2 -
600 documentTopPad_DocumentWidget_(d) - height_Banner(d->banner) / 2; 624 documentTopPad_DocumentView_(d) - height_Banner(d->owner->banner) / 2;
601 rect.size.y = 0; 625 rect.size.y = 0;
602 wasCentered = iTrue; 626 wasCentered = iTrue;
603 } 627 }
604 else if (docSize < rect.size.y - footerHeight_DocumentWidget_(d)) { 628 else if (docSize < rect.size.y - footerHeight_DocumentWidget_(d->owner)) {
605 /* TODO: Phone toolbar? */ 629 /* TODO: Phone toolbar? */
606 /* Center vertically when the document is short. */ 630 /* Center vertically when the document is short. */
607 const int relMidY = (height_Rect(bounds) - footerHeight_DocumentWidget_(d)) / 2; 631 const int relMidY = (height_Rect(bounds) - footerHeight_DocumentWidget_(d->owner)) / 2;
608 const int visHeight = size_GmDocument(d->doc).y; 632 const int visHeight = size_GmDocument(d->doc).y;
609 const int offset = -height_Banner(d->banner) - documentTopPad_DocumentWidget_(d); 633 const int offset = -height_Banner(d->owner->banner) - documentTopPad_DocumentView_(d);
610 rect.pos.y = top_Rect(bounds) + iMaxi(0, relMidY - visHeight / 2 + offset); 634 rect.pos.y = top_Rect(bounds) + iMaxi(0, relMidY - visHeight / 2 + offset);
611 rect.size.y = size_GmDocument(d->doc).y + documentTopMargin_DocumentWidget_(d); 635 rect.size.y = size_GmDocument(d->doc).y + documentTopMargin_DocumentView_(d);
612 wasCentered = iTrue; 636 wasCentered = iTrue;
613 } 637 }
614 } 638 }
615 if (!wasCentered) { 639 if (!wasCentered) {
616 /* The banner overtakes the top margin. */ 640 /* The banner overtakes the top margin. */
617 if (!isEmpty_Banner(d->banner)) { 641 if (!isEmpty_Banner(d->owner->banner)) {
618 rect.pos.y -= margin; 642 rect.pos.y -= margin;
619 } 643 }
620 else { 644 else {
@@ -624,26 +648,28 @@ static iRect documentBounds_DocumentWidget_(const iDocumentWidget *d) {
624 return rect; 648 return rect;
625} 649}
626 650
627static int viewPos_DocumentWidget_(const iDocumentWidget *d) { 651static int viewPos_DocumentView_(const iDocumentView *d) {
628 return height_Banner(d->banner) + documentTopPad_DocumentWidget_(d) - pos_SmoothScroll(&d->scrollY); 652 return height_Banner(d->owner->banner) + documentTopPad_DocumentView_(d) -
653 pos_SmoothScroll(&d->scrollY);
629} 654}
630 655
631static iInt2 documentPos_DocumentWidget_(const iDocumentWidget *d, iInt2 pos) { 656static iInt2 documentPos_DocumentView_(const iDocumentView *d, iInt2 pos) {
632 return addY_I2(sub_I2(pos, topLeft_Rect(documentBounds_DocumentWidget_(d))), 657 return addY_I2(sub_I2(pos, topLeft_Rect(documentBounds_DocumentView_(d))),
633 -viewPos_DocumentWidget_(d)); 658 -viewPos_DocumentView_(d));
634} 659}
635 660
636static iRangei visibleRange_DocumentWidget_(const iDocumentWidget *d) { 661static iRangei visibleRange_DocumentView_(const iDocumentView *d) {
637 int top = pos_SmoothScroll(&d->scrollY) - height_Banner(d->banner) - documentTopPad_DocumentWidget_(d); 662 int top = pos_SmoothScroll(&d->scrollY) - height_Banner(d->owner->banner) -
638 if (isEmpty_Banner(d->banner)) { 663 documentTopPad_DocumentView_(d);
664 if (isEmpty_Banner(d->owner->banner)) {
639 /* Top padding is not collapsed. */ 665 /* Top padding is not collapsed. */
640 top -= d->pageMargin * gap_UI; 666 top -= d->pageMargin * gap_UI;
641 } 667 }
642 return (iRangei){ top, top + height_Rect(bounds_Widget(constAs_Widget(d))) }; 668 return (iRangei){ top, top + height_Rect(bounds_Widget(constAs_Widget(d->owner))) };
643} 669}
644 670
645static void addVisible_DocumentWidget_(void *context, const iGmRun *run) { 671static void addVisible_DocumentView_(void *context, const iGmRun *run) {
646 iDocumentWidget *d = context; 672 iDocumentView *d = context;
647 if (~run->flags & decoration_GmRunFlag && !run->mediaId) { 673 if (~run->flags & decoration_GmRunFlag && !run->mediaId) {
648 if (!d->visibleRuns.start) { 674 if (!d->visibleRuns.start) {
649 d->visibleRuns.start = run; 675 d->visibleRuns.start = run;
@@ -666,7 +692,7 @@ static void addVisible_DocumentWidget_(void *context, const iGmRun *run) {
666 } 692 }
667} 693}
668 694
669static const iGmRun *lastVisibleLink_DocumentWidget_(const iDocumentWidget *d) { 695static const iGmRun *lastVisibleLink_DocumentView_(const iDocumentView *d) {
670 iReverseConstForEach(PtrArray, i, &d->visibleLinks) { 696 iReverseConstForEach(PtrArray, i, &d->visibleLinks) {
671 const iGmRun *run = i.ptr; 697 const iGmRun *run = i.ptr;
672 if (run->flags & decoration_GmRunFlag && run->linkId) { 698 if (run->flags & decoration_GmRunFlag && run->linkId) {
@@ -676,8 +702,8 @@ static const iGmRun *lastVisibleLink_DocumentWidget_(const iDocumentWidget *d) {
676 return NULL; 702 return NULL;
677} 703}
678 704
679static float normScrollPos_DocumentWidget_(const iDocumentWidget *d) { 705static float normScrollPos_DocumentView_(const iDocumentView *d) {
680 const int docSize = pageHeight_DocumentWidget_(d); // size_GmDocument(d->doc).y; 706 const int docSize = pageHeight_DocumentView_(d);
681 if (docSize) { 707 if (docSize) {
682 float pos = pos_SmoothScroll(&d->scrollY) / (float) docSize; 708 float pos = pos_SmoothScroll(&d->scrollY) / (float) docSize;
683 return iMax(pos, 0.0f); 709 return iMax(pos, 0.0f);
@@ -685,15 +711,15 @@ static float normScrollPos_DocumentWidget_(const iDocumentWidget *d) {
685 return 0; 711 return 0;
686} 712}
687 713
688static int scrollMax_DocumentWidget_(const iDocumentWidget *d) { 714static int scrollMax_DocumentView_(const iDocumentView *d) {
689 const iWidget *w = constAs_Widget(d); 715 const iWidget *w = constAs_Widget(d->owner);
690 int sm = pageHeight_DocumentWidget_(d) + 716 int sm = pageHeight_DocumentView_(d) +
691 (isEmpty_Banner(d->banner) ? 2 : 1) * d->pageMargin * gap_UI + /* top and bottom margins */ 717 (isEmpty_Banner(d->owner->banner) ? 2 : 1) * d->pageMargin * gap_UI + /* top and bottom margins */
692 footerHeight_DocumentWidget_(d) - height_Rect(bounds_Widget(w)); 718 footerHeight_DocumentWidget_(d->owner) - height_Rect(bounds_Widget(w));
693 return sm; 719 return sm;
694} 720}
695 721
696static void invalidateLink_DocumentWidget_(iDocumentWidget *d, iGmLinkId id) { 722static void invalidateLink_DocumentView_(iDocumentView *d, iGmLinkId id) {
697 /* A link has multiple runs associated with it. */ 723 /* A link has multiple runs associated with it. */
698 iConstForEach(PtrArray, i, &d->visibleLinks) { 724 iConstForEach(PtrArray, i, &d->visibleLinks) {
699 const iGmRun *run = i.ptr; 725 const iGmRun *run = i.ptr;
@@ -703,7 +729,7 @@ static void invalidateLink_DocumentWidget_(iDocumentWidget *d, iGmLinkId id) {
703 } 729 }
704} 730}
705 731
706static void invalidateVisibleLinks_DocumentWidget_(iDocumentWidget *d) { 732static void invalidateVisibleLinks_DocumentView_(iDocumentView *d) {
707 iConstForEach(PtrArray, i, &d->visibleLinks) { 733 iConstForEach(PtrArray, i, &d->visibleLinks) {
708 const iGmRun *run = i.ptr; 734 const iGmRun *run = i.ptr;
709 if (run->linkId) { 735 if (run->linkId) {
@@ -712,7 +738,7 @@ static void invalidateVisibleLinks_DocumentWidget_(iDocumentWidget *d) {
712 } 738 }
713} 739}
714 740
715static int runOffset_DocumentWidget_(const iDocumentWidget *d, const iGmRun *run) { 741static int runOffset_DocumentView_(const iDocumentView *d, const iGmRun *run) {
716 if (preId_GmRun(run) && run->flags & wide_GmRunFlag) { 742 if (preId_GmRun(run) && run->flags & wide_GmRunFlag) {
717 if (d->animWideRunId == preId_GmRun(run)) { 743 if (d->animWideRunId == preId_GmRun(run)) {
718 return -value_Anim(&d->animWideRunOffset); 744 return -value_Anim(&d->animWideRunOffset);
@@ -726,21 +752,20 @@ static int runOffset_DocumentWidget_(const iDocumentWidget *d, const iGmRun *run
726 return 0; 752 return 0;
727} 753}
728 754
729static void invalidateWideRunsWithNonzeroOffset_DocumentWidget_(iDocumentWidget *d) { 755static void invalidateWideRunsWithNonzeroOffset_DocumentView_(iDocumentView *d) {
730 iConstForEach(PtrArray, i, &d->visibleWideRuns) { 756 iConstForEach(PtrArray, i, &d->visibleWideRuns) {
731 const iGmRun *run = i.ptr; 757 const iGmRun *run = i.ptr;
732 if (runOffset_DocumentWidget_(d, run)) { 758 if (runOffset_DocumentView_(d, run)) {
733 insert_PtrSet(d->invalidRuns, run); 759 insert_PtrSet(d->invalidRuns, run);
734 } 760 }
735 } 761 }
736} 762}
737 763
738static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse);
739
740static void animate_DocumentWidget_(void *ticker) { 764static void animate_DocumentWidget_(void *ticker) {
741 iDocumentWidget *d = ticker; 765 iDocumentWidget *d = ticker;
766 iAssert(isInstance_Object(d, &Class_DocumentWidget));
742 refresh_Widget(d); 767 refresh_Widget(d);
743 if (!isFinished_Anim(&d->sideOpacity) || !isFinished_Anim(&d->altTextOpacity) || 768 if (!isFinished_Anim(&d->view.sideOpacity) || !isFinished_Anim(&d->view.altTextOpacity) ||
744 (d->linkInfo && !isFinished_Anim(&d->linkInfo->opacity))) { 769 (d->linkInfo && !isFinished_Anim(&d->linkInfo->opacity))) {
745 addTicker_App(animate_DocumentWidget_, d); 770 addTicker_App(animate_DocumentWidget_, d);
746 } 771 }
@@ -768,15 +793,15 @@ static iBool isHoverAllowed_DocumentWidget_(const iDocumentWidget *d) {
768 return iTrue; 793 return iTrue;
769} 794}
770 795
771static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) { 796static void updateHover_DocumentView_(iDocumentView *d, iInt2 mouse) {
772 const iWidget *w = constAs_Widget(d); 797 const iWidget *w = constAs_Widget(d->owner);
773 const iRect docBounds = documentBounds_DocumentWidget_(d); 798 const iRect docBounds = documentBounds_DocumentView_(d);
774 const iGmRun * oldHoverLink = d->hoverLink; 799 const iGmRun * oldHoverLink = d->hoverLink;
775 d->hoverPre = NULL; 800 d->hoverPre = NULL;
776 d->hoverLink = NULL; 801 d->hoverLink = NULL;
777 const iInt2 hoverPos = addY_I2(sub_I2(mouse, topLeft_Rect(docBounds)), 802 const iInt2 hoverPos = addY_I2(sub_I2(mouse, topLeft_Rect(docBounds)),
778 -viewPos_DocumentWidget_(d)); 803 -viewPos_DocumentView_(d));
779 if (isHoverAllowed_DocumentWidget_(d)) { 804 if (isHoverAllowed_DocumentWidget_(d->owner)) {
780 iConstForEach(PtrArray, i, &d->visibleLinks) { 805 iConstForEach(PtrArray, i, &d->visibleLinks) {
781 const iGmRun *run = i.ptr; 806 const iGmRun *run = i.ptr;
782 /* Click targets are slightly expanded so there are no gaps between links. */ 807 /* Click targets are slightly expanded so there are no gaps between links. */
@@ -788,19 +813,21 @@ static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) {
788 } 813 }
789 if (d->hoverLink != oldHoverLink) { 814 if (d->hoverLink != oldHoverLink) {
790 if (oldHoverLink) { 815 if (oldHoverLink) {
791 invalidateLink_DocumentWidget_(d, oldHoverLink->linkId); 816 invalidateLink_DocumentView_(d, oldHoverLink->linkId);
792 } 817 }
793 if (d->hoverLink) { 818 if (d->hoverLink) {
794 invalidateLink_DocumentWidget_(d, d->hoverLink->linkId); 819 invalidateLink_DocumentView_(d, d->hoverLink->linkId);
795 } 820 }
796 if (update_LinkInfo(d->linkInfo, d->doc, d->hoverLink ? d->hoverLink->linkId : 0, 821 if (update_LinkInfo(d->owner->linkInfo,
822 d->doc,
823 d->hoverLink ? d->hoverLink->linkId : 0,
797 width_Widget(w))) { 824 width_Widget(w))) {
798 animate_DocumentWidget_(d); 825 animate_DocumentWidget_(d->owner);
799 } 826 }
800 refresh_Widget(w); 827 refresh_Widget(w);
801 } 828 }
802 /* Hovering over preformatted blocks. */ 829 /* Hovering over preformatted blocks. */
803 if (isHoverAllowed_DocumentWidget_(d)) { 830 if (isHoverAllowed_DocumentWidget_(d->owner)) {
804 iConstForEach(PtrArray, j, &d->visiblePre) { 831 iConstForEach(PtrArray, j, &d->visiblePre) {
805 const iGmRun *run = j.ptr; 832 const iGmRun *run = j.ptr;
806 if (contains_Rect(run->bounds, hoverPos)) { 833 if (contains_Rect(run->bounds, hoverPos)) {
@@ -813,18 +840,18 @@ static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) {
813 if (!d->hoverPre) { 840 if (!d->hoverPre) {
814 setValueSpeed_Anim(&d->altTextOpacity, 0.0f, 1.5f); 841 setValueSpeed_Anim(&d->altTextOpacity, 0.0f, 1.5f);
815 if (!isFinished_Anim(&d->altTextOpacity)) { 842 if (!isFinished_Anim(&d->altTextOpacity)) {
816 animate_DocumentWidget_(d); 843 animate_DocumentWidget_(d->owner);
817 } 844 }
818 } 845 }
819 else if (d->hoverPre && 846 else if (d->hoverPre &&
820 preHasAltText_GmDocument(d->doc, preId_GmRun(d->hoverPre)) && 847 preHasAltText_GmDocument(d->doc, preId_GmRun(d->hoverPre)) &&
821 ~d->flags & noHoverWhileScrolling_DocumentWidgetFlag) { 848 ~d->owner->flags & noHoverWhileScrolling_DocumentWidgetFlag) {
822 setValueSpeed_Anim(&d->altTextOpacity, 1.0f, 1.5f); 849 setValueSpeed_Anim(&d->altTextOpacity, 1.0f, 1.5f);
823 if (!isFinished_Anim(&d->altTextOpacity)) { 850 if (!isFinished_Anim(&d->altTextOpacity)) {
824 animate_DocumentWidget_(d); 851 animate_DocumentWidget_(d->owner);
825 } 852 }
826 } 853 }
827 if (isHover_Widget(w) && !contains_Widget(constAs_Widget(d->scroll), mouse)) { 854 if (isHover_Widget(w) && !contains_Widget(constAs_Widget(d->owner->scroll), mouse)) {
828 setCursor_Window(get_Window(), 855 setCursor_Window(get_Window(),
829 d->hoverLink || d->hoverPre ? SDL_SYSTEM_CURSOR_HAND 856 d->hoverLink || d->hoverPre ? SDL_SYSTEM_CURSOR_HAND
830 : SDL_SYSTEM_CURSOR_IBEAM); 857 : SDL_SYSTEM_CURSOR_IBEAM);
@@ -835,15 +862,14 @@ static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) {
835 } 862 }
836} 863}
837 864
838static void updateSideOpacity_DocumentWidget_(iDocumentWidget *d, iBool isAnimated) { 865static void updateSideOpacity_DocumentView_(iDocumentView *d, iBool isAnimated) {
839 float opacity = 0.0f; 866 float opacity = 0.0f;
840// const iGmRun *banner = siteBanner_GmDocument(d->doc); 867 if (!isEmpty_Banner(d->owner->banner) &&
841 if (!isEmpty_Banner(d->banner) && height_Banner(d->banner) < pos_SmoothScroll(&d->scrollY)) { 868 height_Banner(d->owner->banner) < pos_SmoothScroll(&d->scrollY)) {
842// if (banner && bottom_Rect(banner->visBounds) < pos_SmoothScroll(&d->scrollY)) {
843 opacity = 1.0f; 869 opacity = 1.0f;
844 } 870 }
845 setValue_Anim(&d->sideOpacity, opacity, isAnimated ? (opacity < 0.5f ? 100 : 200) : 0); 871 setValue_Anim(&d->sideOpacity, opacity, isAnimated ? (opacity < 0.5f ? 100 : 200) : 0);
846 animate_DocumentWidget_(d); 872 animate_DocumentWidget_(d->owner);
847} 873}
848 874
849static uint32_t mediaUpdateInterval_DocumentWidget_(const iDocumentWidget *d) { 875static uint32_t mediaUpdateInterval_DocumentWidget_(const iDocumentWidget *d) {
@@ -855,10 +881,10 @@ static uint32_t mediaUpdateInterval_DocumentWidget_(const iDocumentWidget *d) {
855 } 881 }
856 static const uint32_t invalidInterval_ = ~0u; 882 static const uint32_t invalidInterval_ = ~0u;
857 uint32_t interval = invalidInterval_; 883 uint32_t interval = invalidInterval_;
858 iConstForEach(PtrArray, i, &d->visibleMedia) { 884 iConstForEach(PtrArray, i, &d->view.visibleMedia) {
859 const iGmRun *run = i.ptr; 885 const iGmRun *run = i.ptr;
860 if (run->mediaType == audio_MediaType) { 886 if (run->mediaType == audio_MediaType) {
861 iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run)); 887 iPlayer *plr = audioPlayer_Media(media_GmDocument(d->view.doc), mediaId_GmRun(run));
862 if (flags_Player(plr) & adjustingVolume_PlayerFlag || 888 if (flags_Player(plr) & adjustingVolume_PlayerFlag ||
863 (isStarted_Player(plr) && !isPaused_Player(plr))) { 889 (isStarted_Player(plr) && !isPaused_Player(plr))) {
864 interval = iMin(interval, 1000 / 15); 890 interval = iMin(interval, 1000 / 15);
@@ -881,10 +907,10 @@ static uint32_t postMediaUpdate_DocumentWidget_(uint32_t interval, void *context
881static void updateMedia_DocumentWidget_(iDocumentWidget *d) { 907static void updateMedia_DocumentWidget_(iDocumentWidget *d) {
882 if (document_App() == d) { 908 if (document_App() == d) {
883 refresh_Widget(d); 909 refresh_Widget(d);
884 iConstForEach(PtrArray, i, &d->visibleMedia) { 910 iConstForEach(PtrArray, i, &d->view.visibleMedia) {
885 const iGmRun *run = i.ptr; 911 const iGmRun *run = i.ptr;
886 if (run->mediaType == audio_MediaType) { 912 if (run->mediaType == audio_MediaType) {
887 iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run)); 913 iPlayer *plr = audioPlayer_Media(media_GmDocument(d->view.doc), mediaId_GmRun(run));
888 if (idleTimeMs_Player(plr) > 3000 && ~flags_Player(plr) & volumeGrabbed_PlayerFlag && 914 if (idleTimeMs_Player(plr) > 3000 && ~flags_Player(plr) & volumeGrabbed_PlayerFlag &&
889 flags_Player(plr) & adjustingVolume_PlayerFlag) { 915 flags_Player(plr) & adjustingVolume_PlayerFlag) {
890 setFlags_Player(plr, adjustingVolume_PlayerFlag, iFalse); 916 setFlags_Player(plr, adjustingVolume_PlayerFlag, iFalse);
@@ -912,7 +938,7 @@ static void animateMedia_DocumentWidget_(iDocumentWidget *d) {
912 } 938 }
913} 939}
914 940
915static iRangecc currentHeading_DocumentWidget_(const iDocumentWidget *d) { 941static iRangecc currentHeading_DocumentView_(const iDocumentView *d) {
916 iRangecc heading = iNullRange; 942 iRangecc heading = iNullRange;
917 if (d->visibleRuns.start) { 943 if (d->visibleRuns.start) {
918 iConstForEach(Array, i, headings_GmDocument(d->doc)) { 944 iConstForEach(Array, i, headings_GmDocument(d->doc)) {
@@ -930,65 +956,68 @@ static iRangecc currentHeading_DocumentWidget_(const iDocumentWidget *d) {
930 return heading; 956 return heading;
931} 957}
932 958
933static int updateScrollMax_DocumentWidget_(iDocumentWidget *d) { 959static int updateScrollMax_DocumentView_(iDocumentView *d) {
934 arrange_Widget(d->footerButtons); /* scrollMax depends on footer height */ 960 arrange_Widget(d->owner->footerButtons); /* scrollMax depends on footer height */
935 const int scrollMax = scrollMax_DocumentWidget_(d); 961 const int scrollMax = scrollMax_DocumentView_(d);
936 setMax_SmoothScroll(&d->scrollY, scrollMax); 962 setMax_SmoothScroll(&d->scrollY, scrollMax);
937 return scrollMax; 963 return scrollMax;
938} 964}
939 965
940static void updateVisible_DocumentWidget_(iDocumentWidget *d) { 966static void updateVisible_DocumentView_(iDocumentView *d) {
941 iChangeFlags(d->flags, 967 /* TODO: The concerns of Widget and View are too tangled together here. */
968 iChangeFlags(d->owner->flags,
942 centerVertically_DocumentWidgetFlag, 969 centerVertically_DocumentWidgetFlag,
943 prefs_App()->centerShortDocs || startsWithCase_String(d->mod.url, "about:") || 970 prefs_App()->centerShortDocs || startsWithCase_String(d->owner->mod.url, "about:") ||
944 !isSuccess_GmStatusCode(d->sourceStatus)); 971 !isSuccess_GmStatusCode(d->owner->sourceStatus));
945 const iRangei visRange = visibleRange_DocumentWidget_(d); 972 iScrollWidget *scrollBar = d->owner->scroll;
973 const iRangei visRange = visibleRange_DocumentView_(d);
946// printf("visRange: %d...%d\n", visRange.start, visRange.end); 974// printf("visRange: %d...%d\n", visRange.start, visRange.end);
947 const iRect bounds = bounds_Widget(as_Widget(d)); 975 const iRect bounds = bounds_Widget(as_Widget(d->owner));
948 const int scrollMax = updateScrollMax_DocumentWidget_(d); 976 const int scrollMax = updateScrollMax_DocumentView_(d);
949 /* Reposition the footer buttons as appropriate. */ 977 /* Reposition the footer buttons as appropriate. */
950 setRange_ScrollWidget(d->scroll, (iRangei){ 0, scrollMax }); 978 setRange_ScrollWidget(scrollBar, (iRangei){ 0, scrollMax });
951 const int docSize = pageHeight_DocumentWidget_(d) + footerHeight_DocumentWidget_(d); 979 const int docSize = pageHeight_DocumentView_(d) + footerHeight_DocumentWidget_(d->owner);
952 const float scrollPos = pos_SmoothScroll(&d->scrollY); 980 const float scrollPos = pos_SmoothScroll(&d->scrollY);
953 setThumb_ScrollWidget(d->scroll, 981 setThumb_ScrollWidget(scrollBar,
954 pos_SmoothScroll(&d->scrollY), 982 pos_SmoothScroll(&d->scrollY),
955 docSize > 0 ? height_Rect(bounds) * size_Range(&visRange) / docSize : 0); 983 docSize > 0 ? height_Rect(bounds) * size_Range(&visRange) / docSize : 0);
956 if (d->footerButtons) { 984 if (d->owner->footerButtons) {
957 const iRect bounds = bounds_Widget(as_Widget(d)); 985 const iRect bounds = bounds_Widget(as_Widget(d->owner));
958 const iRect docBounds = documentBounds_DocumentWidget_(d); 986 const iRect docBounds = documentBounds_DocumentView_(d);
959 const int hPad = (width_Rect(bounds) - iMin(120 * gap_UI, width_Rect(docBounds))) / 2; 987 const int hPad = (width_Rect(bounds) - iMin(120 * gap_UI, width_Rect(docBounds))) / 2;
960 const int vPad = 3 * gap_UI; 988 const int vPad = 3 * gap_UI;
961 setPadding_Widget(d->footerButtons, hPad, 0, hPad, vPad); 989 setPadding_Widget(d->owner->footerButtons, hPad, 0, hPad, vPad);
962 d->footerButtons->rect.pos.y = height_Rect(bounds) - footerHeight_DocumentWidget_(d) + 990 d->owner->footerButtons->rect.pos.y = height_Rect(bounds) -
963 (scrollMax > 0 ? scrollMax - scrollPos : 0); 991 footerHeight_DocumentWidget_(d->owner) +
992 (scrollMax > 0 ? scrollMax - scrollPos : 0);
964 } 993 }
965 clear_PtrArray(&d->visibleLinks); 994 clear_PtrArray(&d->visibleLinks);
966 clear_PtrArray(&d->visibleWideRuns); 995 clear_PtrArray(&d->visibleWideRuns);
967 clear_PtrArray(&d->visiblePre); 996 clear_PtrArray(&d->visiblePre);
968 clear_PtrArray(&d->visibleMedia); 997 clear_PtrArray(&d->visibleMedia);
969 const iRangecc oldHeading = currentHeading_DocumentWidget_(d); 998 const iRangecc oldHeading = currentHeading_DocumentView_(d);
970 /* Scan for visible runs. */ { 999 /* Scan for visible runs. */ {
971 iZap(d->visibleRuns); 1000 iZap(d->visibleRuns);
972 render_GmDocument(d->doc, visRange, addVisible_DocumentWidget_, d); 1001 render_GmDocument(d->doc, visRange, addVisible_DocumentView_, d);
973 } 1002 }
974 const iRangecc newHeading = currentHeading_DocumentWidget_(d); 1003 const iRangecc newHeading = currentHeading_DocumentView_(d);
975 if (memcmp(&oldHeading, &newHeading, sizeof(oldHeading))) { 1004 if (memcmp(&oldHeading, &newHeading, sizeof(oldHeading))) {
976 d->drawBufs->flags |= updateSideBuf_DrawBufsFlag; 1005 d->drawBufs->flags |= updateSideBuf_DrawBufsFlag;
977 } 1006 }
978 updateHover_DocumentWidget_(d, mouseCoord_Window(get_Window(), 0)); 1007 updateHover_DocumentView_(d, mouseCoord_Window(get_Window(), 0));
979 updateSideOpacity_DocumentWidget_(d, iTrue); 1008 updateSideOpacity_DocumentView_(d, iTrue);
980 animateMedia_DocumentWidget_(d); 1009 animateMedia_DocumentWidget_(d->owner);
981 /* Remember scroll positions of recently visited pages. */ { 1010 /* Remember scroll positions of recently visited pages. */ {
982 iRecentUrl *recent = mostRecentUrl_History(d->mod.history); 1011 iRecentUrl *recent = mostRecentUrl_History(d->owner->mod.history);
983 if (recent && docSize && d->state == ready_RequestState && 1012 if (recent && docSize && d->owner->state == ready_RequestState &&
984 equal_String(&recent->url, d->mod.url)) { 1013 equal_String(&recent->url, d->owner->mod.url)) {
985 recent->normScrollY = normScrollPos_DocumentWidget_(d); 1014 recent->normScrollY = normScrollPos_DocumentView_(d);
986 } 1015 }
987 } 1016 }
988 /* After scrolling/resizing stops, begin pre-rendering the visbuf contents. */ { 1017 /* After scrolling/resizing stops, begin pre-rendering the visbuf contents. */ {
989 removeTicker_App(prerender_DocumentWidget_, d); 1018 removeTicker_App(prerender_DocumentWidget_, d->owner);
990 remove_Periodic(periodic_App(), d); 1019 remove_Periodic(periodic_App(), d);
991 add_Periodic(periodic_App(), d, "document.render"); 1020 add_Periodic(periodic_App(), d->owner, "document.render");
992 } 1021 }
993} 1022}
994 1023
@@ -1000,8 +1029,8 @@ static void updateWindowTitle_DocumentWidget_(const iDocumentWidget *d) {
1000 return; 1029 return;
1001 } 1030 }
1002 iStringArray *title = iClob(new_StringArray()); 1031 iStringArray *title = iClob(new_StringArray());
1003 if (!isEmpty_String(title_GmDocument(d->doc))) { 1032 if (!isEmpty_String(title_GmDocument(d->view.doc))) {
1004 pushBack_StringArray(title, title_GmDocument(d->doc)); 1033 pushBack_StringArray(title, title_GmDocument(d->view.doc));
1005 } 1034 }
1006 if (!isEmpty_String(d->titleUser)) { 1035 if (!isEmpty_String(d->titleUser)) {
1007 pushBack_StringArray(title, d->titleUser); 1036 pushBack_StringArray(title, d->titleUser);
@@ -1032,7 +1061,7 @@ static void updateWindowTitle_DocumentWidget_(const iDocumentWidget *d) {
1032 setTitle_MainWindow(get_MainWindow(), text); 1061 setTitle_MainWindow(get_MainWindow(), text);
1033 setWindow = iFalse; 1062 setWindow = iFalse;
1034 } 1063 }
1035 const iChar siteIcon = siteIcon_GmDocument(d->doc); 1064 const iChar siteIcon = siteIcon_GmDocument(d->view.doc);
1036 if (siteIcon) { 1065 if (siteIcon) {
1037 if (!isEmpty_String(text)) { 1066 if (!isEmpty_String(text)) {
1038 prependCStr_String(text, " " restore_ColorEscape); 1067 prependCStr_String(text, " " restore_ColorEscape);
@@ -1072,7 +1101,7 @@ static void updateWindowTitle_DocumentWidget_(const iDocumentWidget *d) {
1072 } 1101 }
1073} 1102}
1074 1103
1075static void updateTimestampBuf_DocumentWidget_(const iDocumentWidget *d) { 1104static void updateTimestampBuf_DocumentView_(const iDocumentView *d) {
1076 if (!isExposed_Window(get_Window())) { 1105 if (!isExposed_Window(get_Window())) {
1077 return; 1106 return;
1078 } 1107 }
@@ -1080,17 +1109,22 @@ static void updateTimestampBuf_DocumentWidget_(const iDocumentWidget *d) {
1080 delete_TextBuf(d->drawBufs->timestampBuf); 1109 delete_TextBuf(d->drawBufs->timestampBuf);
1081 d->drawBufs->timestampBuf = NULL; 1110 d->drawBufs->timestampBuf = NULL;
1082 } 1111 }
1083 if (isValid_Time(&d->sourceTime)) { 1112 if (isValid_Time(&d->owner->sourceTime)) {
1084 iString *fmt = timeFormatHourPreference_Lang("page.timestamp"); 1113 iString *fmt = timeFormatHourPreference_Lang("page.timestamp");
1085 d->drawBufs->timestampBuf = newRange_TextBuf( 1114 d->drawBufs->timestampBuf = newRange_TextBuf(
1086 uiLabel_FontId, 1115 uiLabel_FontId,
1087 white_ColorId, 1116 white_ColorId,
1088 range_String(collect_String(format_Time(&d->sourceTime, cstr_String(fmt))))); 1117 range_String(collect_String(format_Time(&d->owner->sourceTime, cstr_String(fmt)))));
1089 delete_String(fmt); 1118 delete_String(fmt);
1090 } 1119 }
1091 d->drawBufs->flags &= ~updateTimestampBuf_DrawBufsFlag; 1120 d->drawBufs->flags &= ~updateTimestampBuf_DrawBufsFlag;
1092} 1121}
1093 1122
1123static void invalidate_DocumentView_(iDocumentView *d) {
1124 invalidate_VisBuf(d->visBuf);
1125 clear_PtrSet(d->invalidRuns);
1126}
1127
1094static void invalidate_DocumentWidget_(iDocumentWidget *d) { 1128static void invalidate_DocumentWidget_(iDocumentWidget *d) {
1095 if (flags_Widget(as_Widget(d)) & destroyPending_WidgetFlag) { 1129 if (flags_Widget(as_Widget(d)) & destroyPending_WidgetFlag) {
1096 return; 1130 return;
@@ -1103,8 +1137,7 @@ static void invalidate_DocumentWidget_(iDocumentWidget *d) {
1103 return; 1137 return;
1104 } 1138 }
1105 d->flags &= ~invalidationPending_DocumentWidgetFlag; 1139 d->flags &= ~invalidationPending_DocumentWidgetFlag;
1106 invalidate_VisBuf(d->visBuf); 1140 invalidate_DocumentView_(&d->view);
1107 clear_PtrSet(d->invalidRuns);
1108// printf("[%p] '%s' invalidated\n", d, cstr_String(id_Widget(as_Widget(d)))); 1141// printf("[%p] '%s' invalidated\n", d, cstr_String(id_Widget(as_Widget(d))));
1109} 1142}
1110 1143
@@ -1113,17 +1146,21 @@ static iRangecc siteText_DocumentWidget_(const iDocumentWidget *d) {
1113 : range_String(d->titleUser); 1146 : range_String(d->titleUser);
1114} 1147}
1115 1148
1116static void documentRunsInvalidated_DocumentWidget_(iDocumentWidget *d) { 1149static void documentRunsInvalidated_DocumentView_(iDocumentView *d) {
1117 d->foundMark = iNullRange;
1118 d->selectMark = iNullRange;
1119 d->hoverPre = NULL; 1150 d->hoverPre = NULL;
1120 d->hoverAltPre = NULL; 1151 d->hoverAltPre = NULL;
1121 d->hoverLink = NULL; 1152 d->hoverLink = NULL;
1122 d->contextLink = NULL;
1123 iZap(d->visibleRuns); 1153 iZap(d->visibleRuns);
1124 iZap(d->renderRuns); 1154 iZap(d->renderRuns);
1125} 1155}
1126 1156
1157static void documentRunsInvalidated_DocumentWidget_(iDocumentWidget *d) {
1158 d->foundMark = iNullRange;
1159 d->selectMark = iNullRange;
1160 d->contextLink = NULL;
1161 documentRunsInvalidated_DocumentView_(&d->view);
1162}
1163
1127iBool isPinned_DocumentWidget_(const iDocumentWidget *d) { 1164iBool isPinned_DocumentWidget_(const iDocumentWidget *d) {
1128 if (deviceType_App() == phone_AppDeviceType) { 1165 if (deviceType_App() == phone_AppDeviceType) {
1129 return iFalse; 1166 return iFalse;
@@ -1150,11 +1187,11 @@ static void showOrHidePinningIndicator_DocumentWidget_(iDocumentWidget *d) {
1150static void documentWasChanged_DocumentWidget_(iDocumentWidget *d) { 1187static void documentWasChanged_DocumentWidget_(iDocumentWidget *d) {
1151 iChangeFlags(d->flags, selecting_DocumentWidgetFlag, iFalse); 1188 iChangeFlags(d->flags, selecting_DocumentWidgetFlag, iFalse);
1152 setFlags_Widget(as_Widget(d), touchDrag_WidgetFlag, iFalse); 1189 setFlags_Widget(as_Widget(d), touchDrag_WidgetFlag, iFalse);
1153 updateVisitedLinks_GmDocument(d->doc); 1190 updateVisitedLinks_GmDocument(d->view.doc);
1154 documentRunsInvalidated_DocumentWidget_(d); 1191 documentRunsInvalidated_DocumentWidget_(d);
1155 updateWindowTitle_DocumentWidget_(d); 1192 updateWindowTitle_DocumentWidget_(d);
1156 updateVisible_DocumentWidget_(d); 1193 updateVisible_DocumentView_(&d->view);
1157 d->drawBufs->flags |= updateSideBuf_DrawBufsFlag; 1194 d->view.drawBufs->flags |= updateSideBuf_DrawBufsFlag;
1158 invalidate_DocumentWidget_(d); 1195 invalidate_DocumentWidget_(d);
1159 refresh_Widget(as_Widget(d)); 1196 refresh_Widget(as_Widget(d));
1160 /* Check for special bookmark tags. */ 1197 /* Check for special bookmark tags. */
@@ -1169,15 +1206,15 @@ static void documentWasChanged_DocumentWidget_(iDocumentWidget *d) {
1169 showOrHidePinningIndicator_DocumentWidget_(d); 1206 showOrHidePinningIndicator_DocumentWidget_(d);
1170 if (~d->flags & fromCache_DocumentWidgetFlag) { 1207 if (~d->flags & fromCache_DocumentWidgetFlag) {
1171 setCachedDocument_History(d->mod.history, 1208 setCachedDocument_History(d->mod.history,
1172 d->doc, /* keeps a ref */ 1209 d->view.doc, /* keeps a ref */
1173 (d->flags & openedFromSidebar_DocumentWidgetFlag) != 0); 1210 (d->flags & openedFromSidebar_DocumentWidgetFlag) != 0);
1174 } 1211 }
1175} 1212}
1176 1213
1177void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) { 1214void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) {
1178 setUrl_GmDocument(d->doc, d->mod.url); 1215 setUrl_GmDocument(d->view.doc, d->mod.url);
1179 const int docWidth = documentWidth_DocumentWidget_(d); 1216 const int docWidth = documentWidth_DocumentView_(&d->view);
1180 setSource_GmDocument(d->doc, 1217 setSource_GmDocument(d->view.doc,
1181 source, 1218 source,
1182 docWidth, 1219 docWidth,
1183 width_Widget(d), 1220 width_Widget(d),
@@ -1188,22 +1225,21 @@ void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) {
1188} 1225}
1189 1226
1190static void replaceDocument_DocumentWidget_(iDocumentWidget *d, iGmDocument *newDoc) { 1227static void replaceDocument_DocumentWidget_(iDocumentWidget *d, iGmDocument *newDoc) {
1191 pauseAllPlayers_Media(media_GmDocument(d->doc), iTrue); 1228 pauseAllPlayers_Media(media_GmDocument(d->view.doc), iTrue);
1192 iRelease(d->doc); 1229 iRelease(d->view.doc);
1193 d->doc = ref_Object(newDoc); 1230 d->view.doc = ref_Object(newDoc);
1194 documentWasChanged_DocumentWidget_(d); 1231 documentWasChanged_DocumentWidget_(d);
1195} 1232}
1196 1233
1197static void updateBanner_DocumentWidget_(iDocumentWidget *d) { 1234static void updateBanner_DocumentWidget_(iDocumentWidget *d) {
1198 setSite_Banner(d->banner, siteText_DocumentWidget_(d), siteIcon_GmDocument(d->doc)); 1235 setSite_Banner(d->banner, siteText_DocumentWidget_(d), siteIcon_GmDocument(d->view.doc));
1199} 1236}
1200 1237
1201static void updateTheme_DocumentWidget_(iDocumentWidget *d) { 1238static void updateTheme_DocumentWidget_(iDocumentWidget *d) {
1202 if (document_App() != d || category_GmStatusCode(d->sourceStatus) == categoryInput_GmStatusCode) { 1239 if (document_App() != d || category_GmStatusCode(d->sourceStatus) == categoryInput_GmStatusCode) {
1203 return; 1240 return;
1204 } 1241 }
1205// setThemeSeed_GmDocument(d->doc, urlThemeSeed_String(d->mod.url)); /* theme palette and icon */ 1242 d->view.drawBufs->flags |= updateTimestampBuf_DrawBufsFlag;
1206 d->drawBufs->flags |= updateTimestampBuf_DrawBufsFlag;
1207 updateBanner_DocumentWidget_(d); 1243 updateBanner_DocumentWidget_(d);
1208} 1244}
1209 1245
@@ -1234,17 +1270,20 @@ static void makeFooterButtons_DocumentWidget_(iDocumentWidget *d, const iMenuIte
1234 addChild_Widget(as_Widget(d), iClob(d->footerButtons)); 1270 addChild_Widget(as_Widget(d), iClob(d->footerButtons));
1235 arrange_Widget(d->footerButtons); 1271 arrange_Widget(d->footerButtons);
1236 arrange_Widget(w); 1272 arrange_Widget(w);
1237 updateVisible_DocumentWidget_(d); /* final placement for the buttons */ 1273 updateVisible_DocumentView_(&d->view); /* final placement for the buttons */
1274}
1275
1276static void resetScroll_DocumentView_(iDocumentView *d) {
1277 reset_SmoothScroll(&d->scrollY);
1278 init_Anim(&d->sideOpacity, 0);
1279 init_Anim(&d->altTextOpacity, 0);
1280 resetWideRuns_DocumentView_(d);
1238} 1281}
1239 1282
1240static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode code, 1283static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode code,
1241 const iString *meta) { 1284 const iString *meta) {
1242 /* TODO: No such thing as an "error page". It should be an empty page with an error banner. */ 1285 iString *src = collectNew_String();
1243 iString *src = collectNew_String();
1244 const iGmError *msg = get_GmError(code); 1286 const iGmError *msg = get_GmError(code);
1245 // appendChar_String(src, msg->icon ? msg->icon : 0x2327); /* X in a box */
1246 //appendFormat_String(src, " %s\n%s", msg->title, msg->info);
1247// iBool useBanner = iTrue;
1248 destroy_Widget(d->footerButtons); 1287 destroy_Widget(d->footerButtons);
1249 d->footerButtons = NULL; 1288 d->footerButtons = NULL;
1250 const iString *serverErrorMsg = NULL; 1289 const iString *serverErrorMsg = NULL;
@@ -1330,7 +1369,7 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode
1330 } 1369 }
1331 /* Make a new document for the error page.*/ 1370 /* Make a new document for the error page.*/
1332 iGmDocument *errorDoc = new_GmDocument(); 1371 iGmDocument *errorDoc = new_GmDocument();
1333 setWidth_GmDocument(errorDoc, documentWidth_DocumentWidget_(d), width_Widget(d)); 1372 setWidth_GmDocument(errorDoc, documentWidth_DocumentView_(&d->view), width_Widget(d));
1334 setUrl_GmDocument(errorDoc, d->mod.url); 1373 setUrl_GmDocument(errorDoc, d->mod.url);
1335 setFormat_GmDocument(errorDoc, gemini_SourceFormat); 1374 setFormat_GmDocument(errorDoc, gemini_SourceFormat);
1336 replaceDocument_DocumentWidget_(d, errorDoc); 1375 replaceDocument_DocumentWidget_(d, errorDoc);
@@ -1340,10 +1379,7 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode
1340 d->state = ready_RequestState; 1379 d->state = ready_RequestState;
1341 setSource_DocumentWidget(d, src); 1380 setSource_DocumentWidget(d, src);
1342 updateTheme_DocumentWidget_(d); 1381 updateTheme_DocumentWidget_(d);
1343 reset_SmoothScroll(&d->scrollY); 1382 resetScroll_DocumentView_(&d->view);
1344 init_Anim(&d->sideOpacity, 0);
1345 init_Anim(&d->altTextOpacity, 0);
1346 resetWideRuns_DocumentWidget_(d);
1347} 1383}
1348 1384
1349static void updateFetchProgress_DocumentWidget_(iDocumentWidget *d) { 1385static void updateFetchProgress_DocumentWidget_(iDocumentWidget *d) {
@@ -1459,9 +1495,9 @@ static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool
1459 "document.save" } }, 1495 "document.save" } },
1460 2); 1496 2);
1461 } 1497 }
1462 if (preloadCoverImage_Gempub(d->sourceGempub, d->doc)) { 1498 if (preloadCoverImage_Gempub(d->sourceGempub, d->view.doc)) {
1463 redoLayout_GmDocument(d->doc); 1499 redoLayout_GmDocument(d->view.doc);
1464 updateVisible_DocumentWidget_(d); 1500 updateVisible_DocumentView_(&d->view);
1465 invalidate_DocumentWidget_(d); 1501 invalidate_DocumentWidget_(d);
1466 } 1502 }
1467 } 1503 }
@@ -1545,6 +1581,14 @@ static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool
1545 } 1581 }
1546} 1582}
1547 1583
1584static void updateWidth_DocumentView_(iDocumentView *d) {
1585 updateWidth_GmDocument(d->doc, documentWidth_DocumentView_(d), width_Widget(d->owner));
1586}
1587
1588static void updateWidthAndRedoLayout_DocumentView_(iDocumentView *d) {
1589 setWidth_GmDocument(d->doc, documentWidth_DocumentView_(d), width_Widget(d->owner));
1590}
1591
1548static void updateDocument_DocumentWidget_(iDocumentWidget *d, 1592static void updateDocument_DocumentWidget_(iDocumentWidget *d,
1549 const iGmResponse *response, 1593 const iGmResponse *response,
1550 iGmDocument *cachedDoc, 1594 iGmDocument *cachedDoc,
@@ -1565,7 +1609,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d,
1565 } 1609 }
1566 clear_String(&d->sourceMime); 1610 clear_String(&d->sourceMime);
1567 d->sourceTime = response->when; 1611 d->sourceTime = response->when;
1568 d->drawBufs->flags |= updateTimestampBuf_DrawBufsFlag; 1612 d->view.drawBufs->flags |= updateTimestampBuf_DrawBufsFlag;
1569 initBlock_String(&str, &response->body); /* Note: Body may be megabytes in size. */ 1613 initBlock_String(&str, &response->body); /* Note: Body may be megabytes in size. */
1570 if (isSuccess_GmStatusCode(statusCode)) { 1614 if (isSuccess_GmStatusCode(statusCode)) {
1571 /* Check the MIME type. */ 1615 /* Check the MIME type. */
@@ -1709,16 +1753,16 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d,
1709 format_String(&str, "=> %s %s\n", 1753 format_String(&str, "=> %s %s\n",
1710 cstr_String(canonicalUrl_String(d->mod.url)), 1754 cstr_String(canonicalUrl_String(d->mod.url)),
1711 linkTitle); 1755 linkTitle);
1712 setData_Media(media_GmDocument(d->doc), 1756 setData_Media(media_GmDocument(d->view.doc),
1713 imgLinkId, 1757 imgLinkId,
1714 mimeStr, 1758 mimeStr,
1715 &response->body, 1759 &response->body,
1716 !isRequestFinished ? partialData_MediaFlag : 0); 1760 !isRequestFinished ? partialData_MediaFlag : 0);
1717 redoLayout_GmDocument(d->doc); 1761 redoLayout_GmDocument(d->view.doc);
1718 } 1762 }
1719 else if (isAudio && !isInitialUpdate) { 1763 else if (isAudio && !isInitialUpdate) {
1720 /* Update the audio content. */ 1764 /* Update the audio content. */
1721 setData_Media(media_GmDocument(d->doc), 1765 setData_Media(media_GmDocument(d->view.doc),
1722 imgLinkId, 1766 imgLinkId,
1723 mimeStr, 1767 mimeStr,
1724 &response->body, 1768 &response->body,
@@ -1748,11 +1792,11 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d,
1748 return; 1792 return;
1749 } 1793 }
1750 d->flags |= drawDownloadCounter_DocumentWidgetFlag; 1794 d->flags |= drawDownloadCounter_DocumentWidgetFlag;
1751 clear_PtrSet(d->invalidRuns); 1795 clear_PtrSet(d->view.invalidRuns);
1752 deinit_String(&str); 1796 deinit_String(&str);
1753 return; 1797 return;
1754 } 1798 }
1755 setFormat_GmDocument(d->doc, docFormat); 1799 setFormat_GmDocument(d->view.doc, docFormat);
1756 /* Convert the source to UTF-8 if needed. */ 1800 /* Convert the source to UTF-8 if needed. */
1757 if (!equalCase_Rangecc(charset, "utf-8")) { 1801 if (!equalCase_Rangecc(charset, "utf-8")) {
1758 set_String(&str, 1802 set_String(&str,
@@ -1761,7 +1805,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d,
1761 } 1805 }
1762 if (cachedDoc) { 1806 if (cachedDoc) {
1763 replaceDocument_DocumentWidget_(d, cachedDoc); 1807 replaceDocument_DocumentWidget_(d, cachedDoc);
1764 updateWidth_GmDocument(d->doc, documentWidth_DocumentWidget_(d), width_Widget(d)); 1808 updateWidth_DocumentView_(&d->view);
1765 } 1809 }
1766 else if (setSource) { 1810 else if (setSource) {
1767 setSource_DocumentWidget(d, &str); 1811 setSource_DocumentWidget(d, &str);
@@ -1840,9 +1884,9 @@ static void cacheDocumentGlyphs_DocumentWidget_(const iDocumentWidget *d) {
1840 /* Just cache the top of the document, since this is what we usually need. */ 1884 /* Just cache the top of the document, since this is what we usually need. */
1841 int maxY = height_Widget(&d->widget) * 2; 1885 int maxY = height_Widget(&d->widget) * 2;
1842 if (maxY == 0) { 1886 if (maxY == 0) {
1843 maxY = size_GmDocument(d->doc).y; 1887 maxY = size_GmDocument(d->view.doc).y;
1844 } 1888 }
1845 render_GmDocument(d->doc, (iRangei){ 0, maxY }, cacheRunGlyphs_, NULL); 1889 render_GmDocument(d->view.doc, (iRangei){ 0, maxY }, cacheRunGlyphs_, NULL);
1846 } 1890 }
1847} 1891}
1848 1892
@@ -1893,7 +1937,7 @@ static void addBannerWarnings_DocumentWidget_(iDocumentWidget *d) {
1893 value_SiteSpec(collectNewRange_String(urlRoot_String(d->mod.url)), 1937 value_SiteSpec(collectNewRange_String(urlRoot_String(d->mod.url)),
1894 dismissWarnings_SiteSpecKey) | 1938 dismissWarnings_SiteSpecKey) |
1895 (!prefs_App()->warnAboutMissingGlyphs ? missingGlyphs_GmDocumentWarning : 0); 1939 (!prefs_App()->warnAboutMissingGlyphs ? missingGlyphs_GmDocumentWarning : 0);
1896 const int warnings = warnings_GmDocument(d->doc) & ~dismissed; 1940 const int warnings = warnings_GmDocument(d->view.doc) & ~dismissed;
1897 if (warnings & missingGlyphs_GmDocumentWarning) { 1941 if (warnings & missingGlyphs_GmDocumentWarning) {
1898 add_Banner(d->banner, warning_BannerType, missingGlyphs_GmStatusCode, NULL, NULL); 1942 add_Banner(d->banner, warning_BannerType, missingGlyphs_GmStatusCode, NULL, NULL);
1899 /* TODO: List one or more of the missing characters and/or their Unicode blocks? */ 1943 /* TODO: List one or more of the missing characters and/or their Unicode blocks? */
@@ -1910,12 +1954,11 @@ static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float n
1910 clear_ObjectList(d->media); 1954 clear_ObjectList(d->media);
1911 delete_Gempub(d->sourceGempub); 1955 delete_Gempub(d->sourceGempub);
1912 d->sourceGempub = NULL; 1956 d->sourceGempub = NULL;
1913 pauseAllPlayers_Media(media_GmDocument(d->doc), iTrue); 1957 pauseAllPlayers_Media(media_GmDocument(d->view.doc), iTrue);
1914 iRelease(d->doc);
1915 destroy_Widget(d->footerButtons); 1958 destroy_Widget(d->footerButtons);
1916 d->footerButtons = NULL; 1959 d->footerButtons = NULL;
1917 d->doc = new_GmDocument(); 1960 iRelease(d->view.doc);
1918 resetWideRuns_DocumentWidget_(d); 1961 d->view.doc = new_GmDocument();
1919 d->state = fetching_RequestState; 1962 d->state = fetching_RequestState;
1920 d->flags |= fromCache_DocumentWidgetFlag; 1963 d->flags |= fromCache_DocumentWidgetFlag;
1921 /* Do the fetch. */ { 1964 /* Do the fetch. */ {
@@ -1927,7 +1970,7 @@ static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float n
1927 format_String(&d->sourceHeader, cstr_Lang("pageinfo.header.cached")); 1970 format_String(&d->sourceHeader, cstr_Lang("pageinfo.header.cached"));
1928 set_Block(&d->sourceContent, &resp->body); 1971 set_Block(&d->sourceContent, &resp->body);
1929 if (!cachedDoc) { 1972 if (!cachedDoc) {
1930 setWidth_GmDocument(d->doc, documentWidth_DocumentWidget_(d), width_Widget(d)); 1973 updateWidthAndRedoLayout_DocumentView_(&d->view);
1931 } 1974 }
1932 updateDocument_DocumentWidget_(d, resp, cachedDoc, iTrue); 1975 updateDocument_DocumentWidget_(d, resp, cachedDoc, iTrue);
1933 clear_Banner(d->banner); 1976 clear_Banner(d->banner);
@@ -1936,14 +1979,13 @@ static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float n
1936 } 1979 }
1937 d->state = ready_RequestState; 1980 d->state = ready_RequestState;
1938 postProcessRequestContent_DocumentWidget_(d, iTrue); 1981 postProcessRequestContent_DocumentWidget_(d, iTrue);
1939 init_Anim(&d->altTextOpacity, 0); 1982 resetScroll_DocumentView_(&d->view);
1940 reset_SmoothScroll(&d->scrollY); 1983 init_Anim(&d->view.scrollY.pos, d->initNormScrollY * pageHeight_DocumentView_(&d->view));
1941 init_Anim(&d->scrollY.pos, d->initNormScrollY * pageHeight_DocumentWidget_(d)); 1984 updateVisible_DocumentView_(&d->view);
1942 updateSideOpacity_DocumentWidget_(d, iFalse); 1985 moveSpan_SmoothScroll(&d->view.scrollY, 0, 0); /* clamp position to new max */
1943 updateVisible_DocumentWidget_(d); 1986 updateSideOpacity_DocumentView_(&d->view, iFalse);
1944 moveSpan_SmoothScroll(&d->scrollY, 0, 0); /* clamp position to new max */
1945 cacheDocumentGlyphs_DocumentWidget_(d); 1987 cacheDocumentGlyphs_DocumentWidget_(d);
1946 d->drawBufs->flags |= updateTimestampBuf_DrawBufsFlag | updateSideBuf_DrawBufsFlag; 1988 d->view.drawBufs->flags |= updateTimestampBuf_DrawBufsFlag | updateSideBuf_DrawBufsFlag;
1947 d->flags &= ~(urlChanged_DocumentWidgetFlag | drawDownloadCounter_DocumentWidgetFlag); 1989 d->flags &= ~(urlChanged_DocumentWidgetFlag | drawDownloadCounter_DocumentWidgetFlag);
1948 postCommandf_Root( 1990 postCommandf_Root(
1949 as_Widget(d)->root, "document.changed doc:%p url:%s", d, cstr_String(d->mod.url)); 1991 as_Widget(d)->root, "document.changed doc:%p url:%s", d, cstr_String(d->mod.url));
@@ -1959,7 +2001,7 @@ static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) {
1959 d, recent->normScrollY, recent->cachedResponse, recent->cachedDoc); 2001 d, recent->normScrollY, recent->cachedResponse, recent->cachedDoc);
1960 if (!recent->cachedDoc) { 2002 if (!recent->cachedDoc) {
1961 /* We have a cached copy now. */ 2003 /* We have a cached copy now. */
1962 setCachedDocument_History(d->mod.history, d->doc, iFalse); 2004 setCachedDocument_History(d->mod.history, d->view.doc, iFalse);
1963 } 2005 }
1964 return iTrue; 2006 return iTrue;
1965 } 2007 }
@@ -1974,23 +2016,25 @@ static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) {
1974} 2016}
1975 2017
1976static void refreshWhileScrolling_DocumentWidget_(iAny *ptr) { 2018static void refreshWhileScrolling_DocumentWidget_(iAny *ptr) {
2019 iAssert(isInstance_Object(ptr, &Class_DocumentWidget));
1977 iDocumentWidget *d = ptr; 2020 iDocumentWidget *d = ptr;
1978 updateVisible_DocumentWidget_(d); 2021 iDocumentView *view = &d->view;
2022 updateVisible_DocumentView_(view);
1979 refresh_Widget(d); 2023 refresh_Widget(d);
1980 if (d->animWideRunId) { 2024 if (view->animWideRunId) {
1981 for (const iGmRun *r = d->animWideRunRange.start; r != d->animWideRunRange.end; r++) { 2025 for (const iGmRun *r = view->animWideRunRange.start; r != view->animWideRunRange.end; r++) {
1982 insert_PtrSet(d->invalidRuns, r); 2026 insert_PtrSet(view->invalidRuns, r);
1983 } 2027 }
1984 } 2028 }
1985 if (isFinished_Anim(&d->animWideRunOffset)) { 2029 if (isFinished_Anim(&view->animWideRunOffset)) {
1986 d->animWideRunId = 0; 2030 view->animWideRunId = 0;
1987 } 2031 }
1988 if (!isFinished_SmoothScroll(&d->scrollY) || !isFinished_Anim(&d->animWideRunOffset)) { 2032 if (!isFinished_SmoothScroll(&view->scrollY) || !isFinished_Anim(&view->animWideRunOffset)) {
1989 addTicker_App(refreshWhileScrolling_DocumentWidget_, d); 2033 addTicker_App(refreshWhileScrolling_DocumentWidget_, d);
1990 } 2034 }
1991 if (isFinished_SmoothScroll(&d->scrollY)) { 2035 if (isFinished_SmoothScroll(&view->scrollY)) {
1992 iChangeFlags(d->flags, noHoverWhileScrolling_DocumentWidgetFlag, iFalse); 2036 iChangeFlags(d->flags, noHoverWhileScrolling_DocumentWidgetFlag, iFalse);
1993 updateHover_DocumentWidget_(d, mouseCoord_Window(get_Window(), 0)); 2037 updateHover_DocumentView_(view, mouseCoord_Window(get_Window(), 0));
1994 } 2038 }
1995} 2039}
1996 2040
@@ -1999,16 +2043,16 @@ static void scrollBegan_DocumentWidget_(iAnyObject *any, int offset, uint32_t du
1999 /* Get rid of link numbers when scrolling. */ 2043 /* Get rid of link numbers when scrolling. */
2000 if (offset && d->flags & showLinkNumbers_DocumentWidgetFlag) { 2044 if (offset && d->flags & showLinkNumbers_DocumentWidgetFlag) {
2001 setLinkNumberMode_DocumentWidget_(d, iFalse); 2045 setLinkNumberMode_DocumentWidget_(d, iFalse);
2002 invalidateVisibleLinks_DocumentWidget_(d); 2046 invalidateVisibleLinks_DocumentView_(&d->view);
2003 } 2047 }
2004 /* Show and hide toolbar on scroll. */ 2048 /* Show and hide toolbar on scroll. */
2005 if (deviceType_App() == phone_AppDeviceType) { 2049 if (deviceType_App() == phone_AppDeviceType) {
2006 const float normPos = normScrollPos_DocumentWidget_(d); 2050 const float normPos = normScrollPos_DocumentView_(&d->view);
2007 if (prefs_App()->hideToolbarOnScroll && iAbs(offset) > 5 && normPos >= 0) { 2051 if (prefs_App()->hideToolbarOnScroll && iAbs(offset) > 5 && normPos >= 0) {
2008 showToolbar_Root(as_Widget(d)->root, offset < 0); 2052 showToolbar_Root(as_Widget(d)->root, offset < 0);
2009 } 2053 }
2010 } 2054 }
2011 updateVisible_DocumentWidget_(d); 2055 updateVisible_DocumentView_(&d->view);
2012 refresh_Widget(as_Widget(d)); 2056 refresh_Widget(as_Widget(d));
2013 if (duration > 0) { 2057 if (duration > 0) {
2014 iChangeFlags(d->flags, noHoverWhileScrolling_DocumentWidgetFlag, iTrue); 2058 iChangeFlags(d->flags, noHoverWhileScrolling_DocumentWidgetFlag, iTrue);
@@ -2016,47 +2060,47 @@ static void scrollBegan_DocumentWidget_(iAnyObject *any, int offset, uint32_t du
2016 } 2060 }
2017} 2061}
2018 2062
2019static void clampScroll_DocumentWidget_(iDocumentWidget *d) { 2063static void clampScroll_DocumentView_(iDocumentView *d) {
2020 move_SmoothScroll(&d->scrollY, 0); 2064 move_SmoothScroll(&d->scrollY, 0);
2021} 2065}
2022 2066
2023static void immediateScroll_DocumentWidget_(iDocumentWidget *d, int offset) { 2067static void immediateScroll_DocumentView_(iDocumentView *d, int offset) {
2024 move_SmoothScroll(&d->scrollY, offset); 2068 move_SmoothScroll(&d->scrollY, offset);
2025} 2069}
2026 2070
2027static void smoothScroll_DocumentWidget_(iDocumentWidget *d, int offset, int duration) { 2071static void smoothScroll_DocumentView_(iDocumentView *d, int offset, int duration) {
2028 moveSpan_SmoothScroll(&d->scrollY, offset, duration); 2072 moveSpan_SmoothScroll(&d->scrollY, offset, duration);
2029} 2073}
2030 2074
2031static void scrollTo_DocumentWidget_(iDocumentWidget *d, int documentY, iBool centered) { 2075static void scrollTo_DocumentView_(iDocumentView *d, int documentY, iBool centered) {
2032 if (!isEmpty_Banner(d->banner)) { 2076 if (!isEmpty_Banner(d->owner->banner)) {
2033 documentY += height_Banner(d->banner) + documentTopPad_DocumentWidget_(d); 2077 documentY += height_Banner(d->owner->banner) + documentTopPad_DocumentView_(d);
2034 } 2078 }
2035 else { 2079 else {
2036 documentY += documentTopPad_DocumentWidget_(d) + d->pageMargin * gap_UI; 2080 documentY += documentTopPad_DocumentView_(d) + d->pageMargin * gap_UI;
2037 } 2081 }
2038 init_Anim(&d->scrollY.pos, 2082 init_Anim(&d->scrollY.pos,
2039 documentY - (centered ? documentBounds_DocumentWidget_(d).size.y / 2 2083 documentY - (centered ? documentBounds_DocumentView_(d).size.y / 2
2040 : lineHeight_Text(paragraph_FontId))); 2084 : lineHeight_Text(paragraph_FontId)));
2041 clampScroll_DocumentWidget_(d); 2085 clampScroll_DocumentView_(d);
2042} 2086}
2043 2087
2044static void scrollToHeading_DocumentWidget_(iDocumentWidget *d, const char *heading) { 2088static void scrollToHeading_DocumentView_(iDocumentView *d, const char *heading) {
2045 iConstForEach(Array, h, headings_GmDocument(d->doc)) { 2089 iConstForEach(Array, h, headings_GmDocument(d->doc)) {
2046 const iGmHeading *head = h.value; 2090 const iGmHeading *head = h.value;
2047 if (startsWithCase_Rangecc(head->text, heading)) { 2091 if (startsWithCase_Rangecc(head->text, heading)) {
2048 postCommandf_Root(as_Widget(d)->root, "document.goto loc:%p", head->text.start); 2092 postCommandf_Root(as_Widget(d->owner)->root, "document.goto loc:%p", head->text.start);
2049 break; 2093 break;
2050 } 2094 }
2051 } 2095 }
2052} 2096}
2053 2097
2054static iBool scrollWideBlock_DocumentWidget_(iDocumentWidget *d, iInt2 mousePos, int delta, 2098static iBool scrollWideBlock_DocumentView_(iDocumentView *d, iInt2 mousePos, int delta,
2055 int duration) { 2099 int duration) {
2056 if (delta == 0 || d->flags & eitherWheelSwipe_DocumentWidgetFlag) { 2100 if (delta == 0 || d->owner->flags & eitherWheelSwipe_DocumentWidgetFlag) {
2057 return iFalse; 2101 return iFalse;
2058 } 2102 }
2059 const iInt2 docPos = documentPos_DocumentWidget_(d, mousePos); 2103 const iInt2 docPos = documentPos_DocumentView_(d, mousePos);
2060 iConstForEach(PtrArray, i, &d->visibleWideRuns) { 2104 iConstForEach(PtrArray, i, &d->visibleWideRuns) {
2061 const iGmRun *run = i.ptr; 2105 const iGmRun *run = i.ptr;
2062 if (docPos.y >= top_Rect(run->bounds) && docPos.y <= bottom_Rect(run->bounds)) { 2106 if (docPos.y >= top_Rect(run->bounds) && docPos.y <= bottom_Rect(run->bounds)) {
@@ -2066,7 +2110,7 @@ static iBool scrollWideBlock_DocumentWidget_(iDocumentWidget *d, iInt2 mousePos,
2066 for (const iGmRun *r = range.start; r != range.end; r++) { 2110 for (const iGmRun *r = range.start; r != range.end; r++) {
2067 maxWidth = iMax(maxWidth, width_Rect(r->visBounds)); 2111 maxWidth = iMax(maxWidth, width_Rect(r->visBounds));
2068 } 2112 }
2069 const int maxOffset = maxWidth - documentWidth_DocumentWidget_(d) + d->pageMargin * gap_UI; 2113 const int maxOffset = maxWidth - documentWidth_DocumentView_(d) + d->pageMargin * gap_UI;
2070 if (size_Array(&d->wideRunOffsets) <= preId_GmRun(run)) { 2114 if (size_Array(&d->wideRunOffsets) <= preId_GmRun(run)) {
2071 resize_Array(&d->wideRunOffsets, preId_GmRun(run) + 1); 2115 resize_Array(&d->wideRunOffsets, preId_GmRun(run) + 1);
2072 } 2116 }
@@ -2079,8 +2123,8 @@ static iBool scrollWideBlock_DocumentWidget_(iDocumentWidget *d, iInt2 mousePos,
2079 insert_PtrSet(d->invalidRuns, r); 2123 insert_PtrSet(d->invalidRuns, r);
2080 } 2124 }
2081 refresh_Widget(d); 2125 refresh_Widget(d);
2082 d->selectMark = iNullRange; 2126 d->owner->selectMark = iNullRange;
2083 d->foundMark = iNullRange; 2127 d->owner->foundMark = iNullRange;
2084 } 2128 }
2085 if (duration) { 2129 if (duration) {
2086 if (d->animWideRunId != preId_GmRun(run) || isFinished_Anim(&d->animWideRunOffset)) { 2130 if (d->animWideRunId != preId_GmRun(run) || isFinished_Anim(&d->animWideRunOffset)) {
@@ -2102,13 +2146,13 @@ static iBool scrollWideBlock_DocumentWidget_(iDocumentWidget *d, iInt2 mousePos,
2102} 2146}
2103 2147
2104static void togglePreFold_DocumentWidget_(iDocumentWidget *d, uint16_t preId) { 2148static void togglePreFold_DocumentWidget_(iDocumentWidget *d, uint16_t preId) {
2105 d->hoverPre = NULL; 2149 d->view.hoverPre = NULL;
2106 d->hoverAltPre = NULL; 2150 d->view.hoverAltPre = NULL;
2107 d->selectMark = iNullRange; 2151 d->selectMark = iNullRange;
2108 foldPre_GmDocument(d->doc, preId); 2152 foldPre_GmDocument(d->view.doc, preId);
2109 redoLayout_GmDocument(d->doc); 2153 redoLayout_GmDocument(d->view.doc);
2110 clampScroll_DocumentWidget_(d); 2154 clampScroll_DocumentView_(&d->view);
2111 updateHover_DocumentWidget_(d, mouseCoord_Window(get_Window(), 0)); 2155 updateHover_DocumentView_(&d->view, mouseCoord_Window(get_Window(), 0));
2112 invalidate_DocumentWidget_(d); 2156 invalidate_DocumentWidget_(d);
2113 refresh_Widget(as_Widget(d)); 2157 refresh_Widget(as_Widget(d));
2114} 2158}
@@ -2188,8 +2232,8 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
2188 equalCase_Rangecc(urlScheme_String(d->mod.url), "gemini")) { 2232 equalCase_Rangecc(urlScheme_String(d->mod.url), "gemini")) {
2189 statusCode = tlsServerCertificateNotVerified_GmStatusCode; 2233 statusCode = tlsServerCertificateNotVerified_GmStatusCode;
2190 } 2234 }
2191 init_Anim(&d->sideOpacity, 0); 2235 init_Anim(&d->view.sideOpacity, 0);
2192 init_Anim(&d->altTextOpacity, 0); 2236 init_Anim(&d->view.altTextOpacity, 0);
2193 format_String(&d->sourceHeader, 2237 format_String(&d->sourceHeader,
2194 "%s%s", 2238 "%s%s",
2195 humanReadableStatusCode_(statusCode), 2239 humanReadableStatusCode_(statusCode),
@@ -2283,17 +2327,17 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
2283 case categorySuccess_GmStatusCode: 2327 case categorySuccess_GmStatusCode:
2284 if (d->flags & urlChanged_DocumentWidgetFlag) { 2328 if (d->flags & urlChanged_DocumentWidgetFlag) {
2285 /* Keep scroll position when reloading the same page. */ 2329 /* Keep scroll position when reloading the same page. */
2286 reset_SmoothScroll(&d->scrollY); 2330 resetScroll_DocumentView_(&d->view);
2287 } 2331 }
2288 d->scrollY.pullActionTriggered = 0; 2332 d->view.scrollY.pullActionTriggered = 0;
2289 pauseAllPlayers_Media(media_GmDocument(d->doc), iTrue); 2333 pauseAllPlayers_Media(media_GmDocument(d->view.doc), iTrue);
2290 iRelease(d->doc); /* new content incoming */ 2334 iReleasePtr(&d->view.doc); /* new content incoming */
2291 d->doc = new_GmDocument();
2292 delete_Gempub(d->sourceGempub); 2335 delete_Gempub(d->sourceGempub);
2293 d->sourceGempub = NULL; 2336 d->sourceGempub = NULL;
2294 destroy_Widget(d->footerButtons); 2337 destroy_Widget(d->footerButtons);
2295 d->footerButtons = NULL; 2338 d->footerButtons = NULL;
2296 resetWideRuns_DocumentWidget_(d); 2339 d->view.doc = new_GmDocument();
2340 resetWideRuns_DocumentView_(&d->view);
2297 updateDocument_DocumentWidget_(d, resp, NULL, iTrue); 2341 updateDocument_DocumentWidget_(d, resp, NULL, iTrue);
2298 break; 2342 break;
2299 case categoryRedirect_GmStatusCode: 2343 case categoryRedirect_GmStatusCode:
@@ -2361,8 +2405,8 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
2361 unlockResponse_GmRequest(d->request); 2405 unlockResponse_GmRequest(d->request);
2362} 2406}
2363 2407
2364static iRangecc sourceLoc_DocumentWidget_(const iDocumentWidget *d, iInt2 pos) { 2408static iRangecc sourceLoc_DocumentView_(const iDocumentView *d, iInt2 pos) {
2365 return findLoc_GmDocument(d->doc, documentPos_DocumentWidget_(d, pos)); 2409 return findLoc_GmDocument(d->doc, documentPos_DocumentView_(d, pos));
2366} 2410}
2367 2411
2368iDeclareType(MiddleRunParams) 2412iDeclareType(MiddleRunParams)
@@ -2385,8 +2429,8 @@ static void find_MiddleRunParams_(void *params, const iGmRun *run) {
2385 } 2429 }
2386} 2430}
2387 2431
2388static const iGmRun *middleRun_DocumentWidget_(const iDocumentWidget *d) { 2432static const iGmRun *middleRun_DocumentView_(const iDocumentView *d) {
2389 iRangei visRange = visibleRange_DocumentWidget_(d); 2433 iRangei visRange = visibleRange_DocumentView_(d);
2390 iMiddleRunParams params = { (visRange.start + visRange.end) / 2, NULL, 0 }; 2434 iMiddleRunParams params = { (visRange.start + visRange.end) / 2, NULL, 0 };
2391 render_GmDocument(d->doc, visRange, find_MiddleRunParams_, &params); 2435 render_GmDocument(d->doc, visRange, find_MiddleRunParams_, &params);
2392 return params.closest; 2436 return params.closest;
@@ -2414,7 +2458,7 @@ static iMediaRequest *findMediaRequest_DocumentWidget_(const iDocumentWidget *d,
2414 2458
2415static iBool requestMedia_DocumentWidget_(iDocumentWidget *d, iGmLinkId linkId, iBool enableFilters) { 2459static iBool requestMedia_DocumentWidget_(iDocumentWidget *d, iGmLinkId linkId, iBool enableFilters) {
2416 if (!findMediaRequest_DocumentWidget_(d, linkId)) { 2460 if (!findMediaRequest_DocumentWidget_(d, linkId)) {
2417 const iString *mediaUrl = absoluteUrl_String(d->mod.url, linkUrl_GmDocument(d->doc, linkId)); 2461 const iString *mediaUrl = absoluteUrl_String(d->mod.url, linkUrl_GmDocument(d->view.doc, linkId));
2418 pushBack_ObjectList(d->media, iClob(new_MediaRequest(d, linkId, mediaUrl, enableFilters))); 2462 pushBack_ObjectList(d->media, iClob(new_MediaRequest(d, linkId, mediaUrl, enableFilters)));
2419 invalidate_DocumentWidget_(d); 2463 invalidate_DocumentWidget_(d);
2420 return iTrue; 2464 return iTrue;
@@ -2423,7 +2467,7 @@ static iBool requestMedia_DocumentWidget_(iDocumentWidget *d, iGmLinkId linkId,
2423} 2467}
2424 2468
2425static iBool isDownloadRequest_DocumentWidget(const iDocumentWidget *d, const iMediaRequest *req) { 2469static iBool isDownloadRequest_DocumentWidget(const iDocumentWidget *d, const iMediaRequest *req) {
2426 return findMediaForLink_Media(constMedia_GmDocument(d->doc), req->linkId, download_MediaType).type != 0; 2470 return findMediaForLink_Media(constMedia_GmDocument(d->view.doc), req->linkId, download_MediaType).type != 0;
2427} 2471}
2428 2472
2429static iBool handleMediaCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) { 2473static iBool handleMediaCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) {
@@ -2447,21 +2491,21 @@ static iBool handleMediaCommand_DocumentWidget_(iDocumentWidget *d, const char *
2447 if (isDownloadRequest_DocumentWidget(d, req) || 2491 if (isDownloadRequest_DocumentWidget(d, req) ||
2448 startsWith_String(&resp->meta, "audio/")) { 2492 startsWith_String(&resp->meta, "audio/")) {
2449 /* TODO: Use a helper? This is same as below except for the partialData flag. */ 2493 /* TODO: Use a helper? This is same as below except for the partialData flag. */
2450 if (setData_Media(media_GmDocument(d->doc), 2494 if (setData_Media(media_GmDocument(d->view.doc),
2451 req->linkId, 2495 req->linkId,
2452 &resp->meta, 2496 &resp->meta,
2453 &resp->body, 2497 &resp->body,
2454 partialData_MediaFlag | allowHide_MediaFlag)) { 2498 partialData_MediaFlag | allowHide_MediaFlag)) {
2455 redoLayout_GmDocument(d->doc); 2499 redoLayout_GmDocument(d->view.doc);
2456 } 2500 }
2457 updateVisible_DocumentWidget_(d); 2501 updateVisible_DocumentView_(&d->view);
2458 invalidate_DocumentWidget_(d); 2502 invalidate_DocumentWidget_(d);
2459 refresh_Widget(as_Widget(d)); 2503 refresh_Widget(as_Widget(d));
2460 } 2504 }
2461 unlockResponse_GmRequest(req->req); 2505 unlockResponse_GmRequest(req->req);
2462 } 2506 }
2463 /* Update the link's progress. */ 2507 /* Update the link's progress. */
2464 invalidateLink_DocumentWidget_(d, req->linkId); 2508 invalidateLink_DocumentView_(&d->view, req->linkId);
2465 refresh_Widget(d); 2509 refresh_Widget(d);
2466 return iTrue; 2510 return iTrue;
2467 } 2511 }
@@ -2472,14 +2516,14 @@ static iBool handleMediaCommand_DocumentWidget_(iDocumentWidget *d, const char *
2472 if (isDownloadRequest_DocumentWidget(d, req) || 2516 if (isDownloadRequest_DocumentWidget(d, req) ||
2473 startsWith_String(meta_GmRequest(req->req), "image/") || 2517 startsWith_String(meta_GmRequest(req->req), "image/") ||
2474 startsWith_String(meta_GmRequest(req->req), "audio/")) { 2518 startsWith_String(meta_GmRequest(req->req), "audio/")) {
2475 setData_Media(media_GmDocument(d->doc), 2519 setData_Media(media_GmDocument(d->view.doc),
2476 req->linkId, 2520 req->linkId,
2477 meta_GmRequest(req->req), 2521 meta_GmRequest(req->req),
2478 body_GmRequest(req->req), 2522 body_GmRequest(req->req),
2479 allowHide_MediaFlag); 2523 allowHide_MediaFlag);
2480 redoLayout_GmDocument(d->doc); 2524 redoLayout_GmDocument(d->view.doc);
2481 iZap(d->visibleRuns); /* pointers invalidated */ 2525 iZap(d->view.visibleRuns); /* pointers invalidated */
2482 updateVisible_DocumentWidget_(d); 2526 updateVisible_DocumentView_(&d->view);
2483 invalidate_DocumentWidget_(d); 2527 invalidate_DocumentWidget_(d);
2484 refresh_Widget(as_Widget(d)); 2528 refresh_Widget(as_Widget(d));
2485 } 2529 }
@@ -2494,8 +2538,8 @@ static iBool handleMediaCommand_DocumentWidget_(iDocumentWidget *d, const char *
2494 return iFalse; 2538 return iFalse;
2495} 2539}
2496 2540
2497static void allocVisBuffer_DocumentWidget_(const iDocumentWidget *d) { 2541static void allocVisBuffer_DocumentView_(const iDocumentView *d) {
2498 const iWidget *w = constAs_Widget(d); 2542 const iWidget *w = constAs_Widget(d->owner);
2499 const iBool isVisible = isVisible_Widget(w); 2543 const iBool isVisible = isVisible_Widget(w);
2500 const iInt2 size = bounds_Widget(w).size; 2544 const iInt2 size = bounds_Widget(w).size;
2501 if (isVisible) { 2545 if (isVisible) {
@@ -2507,12 +2551,12 @@ static void allocVisBuffer_DocumentWidget_(const iDocumentWidget *d) {
2507} 2551}
2508 2552
2509static iBool fetchNextUnfetchedImage_DocumentWidget_(iDocumentWidget *d) { 2553static iBool fetchNextUnfetchedImage_DocumentWidget_(iDocumentWidget *d) {
2510 iConstForEach(PtrArray, i, &d->visibleLinks) { 2554 iConstForEach(PtrArray, i, &d->view.visibleLinks) {
2511 const iGmRun *run = i.ptr; 2555 const iGmRun *run = i.ptr;
2512 if (run->linkId && run->mediaType == none_MediaType && 2556 if (run->linkId && run->mediaType == none_MediaType &&
2513 ~run->flags & decoration_GmRunFlag) { 2557 ~run->flags & decoration_GmRunFlag) {
2514 const int linkFlags = linkFlags_GmDocument(d->doc, run->linkId); 2558 const int linkFlags = linkFlags_GmDocument(d->view.doc, run->linkId);
2515 if (isMediaLink_GmDocument(d->doc, run->linkId) && 2559 if (isMediaLink_GmDocument(d->view.doc, run->linkId) &&
2516 linkFlags & imageFileExtension_GmLinkFlag && 2560 linkFlags & imageFileExtension_GmLinkFlag &&
2517 ~linkFlags & content_GmLinkFlag && ~linkFlags & permanent_GmLinkFlag ) { 2561 ~linkFlags & content_GmLinkFlag && ~linkFlags & permanent_GmLinkFlag ) {
2518 if (requestMedia_DocumentWidget_(d, run->linkId, iTrue)) { 2562 if (requestMedia_DocumentWidget_(d, run->linkId, iTrue)) {
@@ -2570,9 +2614,9 @@ static void addAllLinks_(void *context, const iGmRun *run) {
2570 } 2614 }
2571} 2615}
2572 2616
2573static size_t visibleLinkOrdinal_DocumentWidget_(const iDocumentWidget *d, iGmLinkId linkId) { 2617static size_t visibleLinkOrdinal_DocumentView_(const iDocumentView *d, iGmLinkId linkId) {
2574 size_t ord = 0; 2618 size_t ord = 0;
2575 const iRangei visRange = visibleRange_DocumentWidget_(d); 2619 const iRangei visRange = visibleRange_DocumentView_(d);
2576 iConstForEach(PtrArray, i, &d->visibleLinks) { 2620 iConstForEach(PtrArray, i, &d->visibleLinks) {
2577 const iGmRun *run = i.ptr; 2621 const iGmRun *run = i.ptr;
2578 if (top_Rect(run->visBounds) >= visRange.start + gap_UI * d->pageMargin * 4 / 5) { 2622 if (top_Rect(run->visBounds) >= visRange.start + gap_UI * d->pageMargin * 4 / 5) {
@@ -2598,38 +2642,36 @@ static const int homeRowKeys_[] = {
2598 't', 'y', 2642 't', 'y',
2599}; 2643};
2600 2644
2601static iBool updateDocumentWidthRetainingScrollPosition_DocumentWidget_(iDocumentWidget *d, 2645static iBool updateDocumentWidthRetainingScrollPosition_DocumentView_(iDocumentView *d,
2602 iBool keepCenter) { 2646 iBool keepCenter) {
2603 const int newWidth = documentWidth_DocumentWidget_(d); 2647 const int newWidth = documentWidth_DocumentView_(d);
2604 if (newWidth == size_GmDocument(d->doc).x && !keepCenter /* not a font change */) { 2648 if (newWidth == size_GmDocument(d->doc).x && !keepCenter /* not a font change */) {
2605 return iFalse; 2649 return iFalse;
2606 } 2650 }
2607 /* Font changes (i.e., zooming) will keep the view centered, otherwise keep the top 2651 /* Font changes (i.e., zooming) will keep the view centered, otherwise keep the top
2608 of the visible area fixed. */ 2652 of the visible area fixed. */
2609 const iGmRun *run = keepCenter ? middleRun_DocumentWidget_(d) : d->visibleRuns.start; 2653 const iGmRun *run = keepCenter ? middleRun_DocumentView_(d) : d->visibleRuns.start;
2610 const char * runLoc = (run ? run->text.start : NULL); 2654 const char * runLoc = (run ? run->text.start : NULL);
2611 int voffset = 0; 2655 int voffset = 0;
2612 if (!keepCenter && run) { 2656 if (!keepCenter && run) {
2613 /* Keep the first visible run visible at the same position. */ 2657 /* Keep the first visible run visible at the same position. */
2614 /* TODO: First *fully* visible run? */ 2658 /* TODO: First *fully* visible run? */
2615 voffset = visibleRange_DocumentWidget_(d).start - top_Rect(run->visBounds); 2659 voffset = visibleRange_DocumentView_(d).start - top_Rect(run->visBounds);
2616 } 2660 }
2617 setWidth_GmDocument(d->doc, newWidth, width_Widget(d)); 2661 setWidth_GmDocument(d->doc, newWidth, width_Widget(d->owner));
2618 setWidth_Banner(d->banner, newWidth); 2662 setWidth_Banner(d->owner->banner, newWidth);
2619 documentRunsInvalidated_DocumentWidget_(d); 2663 documentRunsInvalidated_DocumentWidget_(d->owner);
2620 if (runLoc && !keepCenter) { 2664 if (runLoc && !keepCenter) {
2621 run = findRunAtLoc_GmDocument(d->doc, runLoc); 2665 run = findRunAtLoc_GmDocument(d->doc, runLoc);
2622 if (run) { 2666 if (run) {
2623 scrollTo_DocumentWidget_(d, 2667 scrollTo_DocumentView_(
2624 top_Rect(run->visBounds) + 2668 d, top_Rect(run->visBounds) + lineHeight_Text(paragraph_FontId) + voffset, iFalse);
2625 lineHeight_Text(paragraph_FontId) + voffset,
2626 iFalse);
2627 } 2669 }
2628 } 2670 }
2629 else if (runLoc && keepCenter) { 2671 else if (runLoc && keepCenter) {
2630 run = findRunAtLoc_GmDocument(d->doc, runLoc); 2672 run = findRunAtLoc_GmDocument(d->doc, runLoc);
2631 if (run) { 2673 if (run) {
2632 scrollTo_DocumentWidget_(d, mid_Rect(run->bounds).y, iTrue); 2674 scrollTo_DocumentView_(d, mid_Rect(run->bounds).y, iTrue);
2633 } 2675 }
2634 } 2676 }
2635 return iTrue; 2677 return iTrue;
@@ -2662,21 +2704,26 @@ static iBool handlePinch_DocumentWidget_(iDocumentWidget *d, const char *cmd) {
2662 return iTrue; 2704 return iTrue;
2663} 2705}
2664 2706
2707static void swap_DocumentView_(iDocumentView *d, iDocumentView *swapBuffersWith) {
2708 d->scrollY = swapBuffersWith->scrollY;
2709 d->scrollY.widget = as_Widget(d->owner);
2710 iSwap(iVisBuf *, d->visBuf, swapBuffersWith->visBuf);
2711 iSwap(iVisBufMeta *, d->visBufMeta, swapBuffersWith->visBufMeta);
2712 iSwap(iDrawBufs *, d->drawBufs, swapBuffersWith->drawBufs);
2713 updateVisible_DocumentView_(d);
2714 updateVisible_DocumentView_(swapBuffersWith);
2715}
2716
2665static void swap_DocumentWidget_(iDocumentWidget *d, iGmDocument *doc, 2717static void swap_DocumentWidget_(iDocumentWidget *d, iGmDocument *doc,
2666 iDocumentWidget *swapBuffersWith) { 2718 iDocumentWidget *swapBuffersWith) {
2667 if (doc) { 2719 if (doc) {
2668 iAssert(isInstance_Object(doc, &Class_GmDocument)); 2720 iAssert(isInstance_Object(doc, &Class_GmDocument));
2669 replaceDocument_DocumentWidget_(d, doc); 2721 replaceDocument_DocumentWidget_(d, doc);
2670 d->scrollY = swapBuffersWith->scrollY;
2671 d->scrollY.widget = as_Widget(d);
2672 iSwap(iBanner *, d->banner, swapBuffersWith->banner); 2722 iSwap(iBanner *, d->banner, swapBuffersWith->banner);
2673 setOwner_Banner(d->banner, d); 2723 setOwner_Banner(d->banner, d);
2674 setOwner_Banner(swapBuffersWith->banner, swapBuffersWith); 2724 setOwner_Banner(swapBuffersWith->banner, swapBuffersWith);
2675 iSwap(iVisBuf *, d->visBuf, swapBuffersWith->visBuf); 2725 swap_DocumentView_(&d->view, &swapBuffersWith->view);
2676 iSwap(iVisBufMeta *, d->visBufMeta, swapBuffersWith->visBufMeta); 2726// invalidate_DocumentWidget_(swapBuffersWith);
2677 iSwap(iDrawBufs *, d->drawBufs, swapBuffersWith->drawBufs);
2678 updateVisible_DocumentWidget_(d);
2679 invalidate_DocumentWidget_(swapBuffersWith);
2680 } 2727 }
2681} 2728}
2682 2729
@@ -2803,7 +2850,7 @@ static iBool handleSwipe_DocumentWidget_(iDocumentWidget *d, const char *cmd) {
2803 target->widget.rect.pos = windowToInner_Widget(swipeParent, localToWindow_Widget(w, w->rect.pos)); 2850 target->widget.rect.pos = windowToInner_Widget(swipeParent, localToWindow_Widget(w, w->rect.pos));
2804 target->widget.rect.size = d->widget.rect.size; 2851 target->widget.rect.size = d->widget.rect.size;
2805 setFlags_Widget(as_Widget(target), fixedPosition_WidgetFlag | fixedSize_WidgetFlag, iTrue); 2852 setFlags_Widget(as_Widget(target), fixedPosition_WidgetFlag | fixedSize_WidgetFlag, iTrue);
2806 swap_DocumentWidget_(target, d->doc, d); 2853 swap_DocumentWidget_(target, d->view.doc, d);
2807 addChildPos_Widget(swipeParent, iClob(target), front_WidgetAddPos); 2854 addChildPos_Widget(swipeParent, iClob(target), front_WidgetAddPos);
2808 setFlags_Widget(as_Widget(target), refChildrenOffset_WidgetFlag, iTrue); 2855 setFlags_Widget(as_Widget(target), refChildrenOffset_WidgetFlag, iTrue);
2809 as_Widget(target)->offsetRef = parent_Widget(w); 2856 as_Widget(target)->offsetRef = parent_Widget(w);
@@ -2837,7 +2884,7 @@ static iBool handleSwipe_DocumentWidget_(iDocumentWidget *d, const char *cmd) {
2837 /* TODO: Currently not animated! What exactly is the appropriate thing to do here? */ 2884 /* TODO: Currently not animated! What exactly is the appropriate thing to do here? */
2838 iWidget *swipeParent = swipeParent_DocumentWidget_(d); 2885 iWidget *swipeParent = swipeParent_DocumentWidget_(d);
2839 iDocumentWidget *swipeOut = findChild_Widget(swipeParent, "swipeout"); 2886 iDocumentWidget *swipeOut = findChild_Widget(swipeParent, "swipeout");
2840 swap_DocumentWidget_(d, swipeOut->doc, swipeOut); 2887 swap_DocumentWidget_(d, swipeOut->view.doc, swipeOut);
2841// const int visOff = visualOffsetByReference_Widget(w); 2888// const int visOff = visualOffsetByReference_Widget(w);
2842 w->offsetRef = NULL; 2889 w->offsetRef = NULL;
2843// setVisualOffset_Widget(w, visOff, 0, 0); 2890// setVisualOffset_Widget(w, visOff, 0, 0);
@@ -2871,7 +2918,7 @@ static iBool handleSwipe_DocumentWidget_(iDocumentWidget *d, const char *cmd) {
2871 addChildPos_Widget(swipeParent, iClob(target), back_WidgetAddPos); 2918 addChildPos_Widget(swipeParent, iClob(target), back_WidgetAddPos);
2872 setId_Widget(as_Widget(target), "swipeout"); 2919 setId_Widget(as_Widget(target), "swipeout");
2873 setFlags_Widget(as_Widget(target), disabled_WidgetFlag, iTrue); 2920 setFlags_Widget(as_Widget(target), disabled_WidgetFlag, iTrue);
2874 swap_DocumentWidget_(target, d->doc, d); 2921 swap_DocumentWidget_(target, d->view.doc, d);
2875 setUrlAndSource_DocumentWidget(d, 2922 setUrlAndSource_DocumentWidget(d,
2876 swipeIn->mod.url, 2923 swipeIn->mod.url,
2877 collectNewCStr_String("text/gemini"), 2924 collectNewCStr_String("text/gemini"),
@@ -2924,23 +2971,23 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2924 return iFalse; 2971 return iFalse;
2925 } 2972 }
2926 /* When any tab changes its document URL, update the open link indicators. */ 2973 /* When any tab changes its document URL, update the open link indicators. */
2927 if (updateOpenURLs_GmDocument(d->doc)) { 2974 if (updateOpenURLs_GmDocument(d->view.doc)) {
2928 invalidate_DocumentWidget_(d); 2975 invalidate_DocumentWidget_(d);
2929 refresh_Widget(d); 2976 refresh_Widget(d);
2930 } 2977 }
2931 return iFalse; 2978 return iFalse;
2932 } 2979 }
2933 if (equal_Command(cmd, "visited.changed")) { 2980 if (equal_Command(cmd, "visited.changed")) {
2934 updateVisitedLinks_GmDocument(d->doc); 2981 updateVisitedLinks_GmDocument(d->view.doc);
2935 invalidateVisibleLinks_DocumentWidget_(d); 2982 invalidateVisibleLinks_DocumentView_(&d->view);
2936 return iFalse; 2983 return iFalse;
2937 } 2984 }
2938 if (equal_Command(cmd, "document.render")) /* `Periodic` makes direct dispatch to here */ { 2985 if (equal_Command(cmd, "document.render")) /* `Periodic` makes direct dispatch to here */ {
2939// printf("%u: document.render\n", SDL_GetTicks()); 2986// printf("%u: document.render\n", SDL_GetTicks());
2940 if (SDL_GetTicks() - d->drawBufs->lastRenderTime > 150) { 2987 if (SDL_GetTicks() - d->view.drawBufs->lastRenderTime > 150) {
2941 remove_Periodic(periodic_App(), d); 2988 remove_Periodic(periodic_App(), d);
2942 /* Scrolling has stopped, begin filling up the buffer. */ 2989 /* Scrolling has stopped, begin filling up the buffer. */
2943 if (d->visBuf->buffers[0].texture) { 2990 if (d->view.visBuf->buffers[0].texture) {
2944 addTicker_App(prerender_DocumentWidget_, d); 2991 addTicker_App(prerender_DocumentWidget_, d);
2945 } 2992 }
2946 } 2993 }
@@ -2955,11 +3002,11 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2955 setLinkNumberMode_DocumentWidget_(d, iFalse); 3002 setLinkNumberMode_DocumentWidget_(d, iFalse);
2956 d->phoneToolbar = findWidget_App("toolbar"); 3003 d->phoneToolbar = findWidget_App("toolbar");
2957 const iBool keepCenter = equal_Command(cmd, "font.changed"); 3004 const iBool keepCenter = equal_Command(cmd, "font.changed");
2958 updateDocumentWidthRetainingScrollPosition_DocumentWidget_(d, keepCenter); 3005 updateDocumentWidthRetainingScrollPosition_DocumentView_(&d->view, keepCenter);
2959 d->drawBufs->flags |= updateSideBuf_DrawBufsFlag; 3006 d->view.drawBufs->flags |= updateSideBuf_DrawBufsFlag;
2960 updateVisible_DocumentWidget_(d); 3007 updateVisible_DocumentView_(&d->view);
2961 invalidate_DocumentWidget_(d); 3008 invalidate_DocumentWidget_(d);
2962 dealloc_VisBuf(d->visBuf); 3009 dealloc_VisBuf(d->view.visBuf);
2963 updateWindowTitle_DocumentWidget_(d); 3010 updateWindowTitle_DocumentWidget_(d);
2964 showOrHidePinningIndicator_DocumentWidget_(d); 3011 showOrHidePinningIndicator_DocumentWidget_(d);
2965 refresh_Widget(w); 3012 refresh_Widget(w);
@@ -2967,7 +3014,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2967 else if (equal_Command(cmd, "window.focus.lost")) { 3014 else if (equal_Command(cmd, "window.focus.lost")) {
2968 if (d->flags & showLinkNumbers_DocumentWidgetFlag) { 3015 if (d->flags & showLinkNumbers_DocumentWidgetFlag) {
2969 setLinkNumberMode_DocumentWidget_(d, iFalse); 3016 setLinkNumberMode_DocumentWidget_(d, iFalse);
2970 invalidateVisibleLinks_DocumentWidget_(d); 3017 invalidateVisibleLinks_DocumentView_(&d->view);
2971 refresh_Widget(w); 3018 refresh_Widget(w);
2972 } 3019 }
2973 return iFalse; 3020 return iFalse;
@@ -2979,16 +3026,16 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2979 invalidateTheme_History(d->mod.history); /* forget cached color palettes */ 3026 invalidateTheme_History(d->mod.history); /* forget cached color palettes */
2980 if (document_App() == d) { 3027 if (document_App() == d) {
2981 updateTheme_DocumentWidget_(d); 3028 updateTheme_DocumentWidget_(d);
2982 updateVisible_DocumentWidget_(d); 3029 updateVisible_DocumentView_(&d->view);
2983 updateTrust_DocumentWidget_(d, NULL); 3030 updateTrust_DocumentWidget_(d, NULL);
2984 d->drawBufs->flags |= updateSideBuf_DrawBufsFlag; 3031 d->view.drawBufs->flags |= updateSideBuf_DrawBufsFlag;
2985 invalidate_DocumentWidget_(d); 3032 invalidate_DocumentWidget_(d);
2986 refresh_Widget(w); 3033 refresh_Widget(w);
2987 } 3034 }
2988 } 3035 }
2989 else if (equal_Command(cmd, "document.layout.changed") && document_Root(get_Root()) == d) { 3036 else if (equal_Command(cmd, "document.layout.changed") && document_Root(get_Root()) == d) {
2990 if (argLabel_Command(cmd, "redo")) { 3037 if (argLabel_Command(cmd, "redo")) {
2991 redoLayout_GmDocument(d->doc); 3038 redoLayout_GmDocument(d->view.doc);
2992 } 3039 }
2993 updateSize_DocumentWidget(d); 3040 updateSize_DocumentWidget(d);
2994 } 3041 }
@@ -3011,11 +3058,11 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
3011 updateFetchProgress_DocumentWidget_(d); 3058 updateFetchProgress_DocumentWidget_(d);
3012 updateHover_Window(window_Widget(w)); 3059 updateHover_Window(window_Widget(w));
3013 } 3060 }
3014 init_Anim(&d->sideOpacity, 0); 3061 init_Anim(&d->view.sideOpacity, 0);
3015 init_Anim(&d->altTextOpacity, 0); 3062 init_Anim(&d->view.altTextOpacity, 0);
3016 updateSideOpacity_DocumentWidget_(d, iFalse); 3063 updateSideOpacity_DocumentView_(&d->view, iFalse);
3017 updateWindowTitle_DocumentWidget_(d); 3064 updateWindowTitle_DocumentWidget_(d);
3018 allocVisBuffer_DocumentWidget_(d); 3065 allocVisBuffer_DocumentView_(&d->view);
3019 animateMedia_DocumentWidget_(d); 3066 animateMedia_DocumentWidget_(d);
3020 remove_Periodic(periodic_App(), d); 3067 remove_Periodic(periodic_App(), d);
3021 removeTicker_App(prerender_DocumentWidget_, d); 3068 removeTicker_App(prerender_DocumentWidget_, d);
@@ -3039,8 +3086,8 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
3039 selectWords_DocumentWidgetFlag; /* finger-based selection is imprecise */ 3086 selectWords_DocumentWidgetFlag; /* finger-based selection is imprecise */
3040 d->flags &= ~selectLines_DocumentWidgetFlag; 3087 d->flags &= ~selectLines_DocumentWidgetFlag;
3041 setFadeEnabled_ScrollWidget(d->scroll, iFalse); 3088 setFadeEnabled_ScrollWidget(d->scroll, iFalse);
3042 d->selectMark = sourceLoc_DocumentWidget_(d, d->contextPos); 3089 d->selectMark = sourceLoc_DocumentView_(&d->view, d->contextPos);
3043 extendRange_Rangecc(&d->selectMark, range_String(source_GmDocument(d->doc)), 3090 extendRange_Rangecc(&d->selectMark, range_String(source_GmDocument(d->view.doc)),
3044 word_RangeExtension | bothStartAndEnd_RangeExtension); 3091 word_RangeExtension | bothStartAndEnd_RangeExtension);
3045 d->initialSelectMark = d->selectMark; 3092 d->initialSelectMark = d->selectMark;
3046 } 3093 }
@@ -3183,7 +3230,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
3183 } 3230 }
3184 else { 3231 else {
3185 /* Full document. */ 3232 /* Full document. */
3186 copied = copy_String(source_GmDocument(d->doc)); 3233 copied = copy_String(source_GmDocument(d->view.doc));
3187 } 3234 }
3188 SDL_SetClipboardText(cstr_String(copied)); 3235 SDL_SetClipboardText(cstr_String(copied));
3189 delete_String(copied); 3236 delete_String(copied);
@@ -3195,7 +3242,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
3195 else if (equal_Command(cmd, "document.copylink") && document_App() == d) { 3242 else if (equal_Command(cmd, "document.copylink") && document_App() == d) {
3196 if (d->contextLink) { 3243 if (d->contextLink) {
3197 SDL_SetClipboardText(cstr_String(canonicalUrl_String(absoluteUrl_String( 3244 SDL_SetClipboardText(cstr_String(canonicalUrl_String(absoluteUrl_String(
3198 d->mod.url, linkUrl_GmDocument(d->doc, d->contextLink->linkId))))); 3245 d->mod.url, linkUrl_GmDocument(d->view.doc, d->contextLink->linkId)))));
3199 } 3246 }
3200 else { 3247 else {
3201 SDL_SetClipboardText(cstr_String(canonicalUrl_String(d->mod.url))); 3248 SDL_SetClipboardText(cstr_String(canonicalUrl_String(d->mod.url)));
@@ -3205,13 +3252,13 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
3205 else if (equalWidget_Command(cmd, w, "document.downloadlink")) { 3252 else if (equalWidget_Command(cmd, w, "document.downloadlink")) {
3206 if (d->contextLink) { 3253 if (d->contextLink) {
3207 const iGmLinkId linkId = d->contextLink->linkId; 3254 const iGmLinkId linkId = d->contextLink->linkId;
3208 setUrl_Media(media_GmDocument(d->doc), 3255 setUrl_Media(media_GmDocument(d->view.doc),
3209 linkId, 3256 linkId,
3210 download_MediaType, 3257 download_MediaType,
3211 linkUrl_GmDocument(d->doc, linkId)); 3258 linkUrl_GmDocument(d->view.doc, linkId));
3212 requestMedia_DocumentWidget_(d, linkId, iFalse /* no filters */); 3259 requestMedia_DocumentWidget_(d, linkId, iFalse /* no filters */);
3213 redoLayout_GmDocument(d->doc); /* inline downloader becomes visible */ 3260 redoLayout_GmDocument(d->view.doc); /* inline downloader becomes visible */
3214 updateVisible_DocumentWidget_(d); 3261 updateVisible_DocumentView_(&d->view);
3215 invalidate_DocumentWidget_(d); 3262 invalidate_DocumentWidget_(d);
3216 refresh_Widget(w); 3263 refresh_Widget(w);
3217 } 3264 }
@@ -3256,7 +3303,8 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
3256 updateFetchProgress_DocumentWidget_(d); 3303 updateFetchProgress_DocumentWidget_(d);
3257 checkResponse_DocumentWidget_(d); 3304 checkResponse_DocumentWidget_(d);
3258 if (category_GmStatusCode(status_GmRequest(d->request)) == categorySuccess_GmStatusCode) { 3305 if (category_GmStatusCode(status_GmRequest(d->request)) == categorySuccess_GmStatusCode) {
3259 init_Anim(&d->scrollY.pos, d->initNormScrollY * pageHeight_DocumentWidget_(d)); /* TODO: unless user already scrolled! */ 3306 init_Anim(&d->view.scrollY.pos, d->initNormScrollY * pageHeight_DocumentView_(&d->view));
3307 /* TODO: unless user already scrolled! */
3260 } 3308 }
3261 addBannerWarnings_DocumentWidget_(d); 3309 addBannerWarnings_DocumentWidget_(d);
3262 iChangeFlags(d->flags, 3310 iChangeFlags(d->flags,
@@ -3276,8 +3324,8 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
3276 } 3324 }
3277 } 3325 }
3278 iReleasePtr(&d->request); 3326 iReleasePtr(&d->request);
3279 updateVisible_DocumentWidget_(d); 3327 updateVisible_DocumentView_(&d->view);
3280 d->drawBufs->flags |= updateSideBuf_DrawBufsFlag; 3328 d->view.drawBufs->flags |= updateSideBuf_DrawBufsFlag;
3281 postCommandf_Root(w->root, 3329 postCommandf_Root(w->root,
3282 "document.changed doc:%p status:%d url:%s", 3330 "document.changed doc:%p status:%d url:%s",
3283 d, 3331 d,
@@ -3285,7 +3333,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
3285 cstr_String(d->mod.url)); 3333 cstr_String(d->mod.url));
3286 /* Check for a pending goto. */ 3334 /* Check for a pending goto. */
3287 if (!isEmpty_String(&d->pendingGotoHeading)) { 3335 if (!isEmpty_String(&d->pendingGotoHeading)) {
3288 scrollToHeading_DocumentWidget_(d, cstr_String(&d->pendingGotoHeading)); 3336 scrollToHeading_DocumentView_(&d->view, cstr_String(&d->pendingGotoHeading));
3289 clear_String(&d->pendingGotoHeading); 3337 clear_String(&d->pendingGotoHeading);
3290 } 3338 }
3291 cacheDocumentGlyphs_DocumentWidget_(d); 3339 cacheDocumentGlyphs_DocumentWidget_(d);
@@ -3331,7 +3379,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
3331 else if (equal_Command(cmd, "media.player.started")) { 3379 else if (equal_Command(cmd, "media.player.started")) {
3332 /* When one media player starts, pause the others that may be playing. */ 3380 /* When one media player starts, pause the others that may be playing. */
3333 const iPlayer *startedPlr = pointerLabel_Command(cmd, "player"); 3381 const iPlayer *startedPlr = pointerLabel_Command(cmd, "player");
3334 const iMedia * media = media_GmDocument(d->doc); 3382 const iMedia * media = media_GmDocument(d->view.doc);
3335 const size_t num = numAudio_Media(media); 3383 const size_t num = numAudio_Media(media);
3336 for (size_t id = 1; id <= num; id++) { 3384 for (size_t id = 1; id <= num; id++) {
3337 iPlayer *plr = audioPlayer_Media(media, (iMediaId){ audio_MediaType, id }); 3385 iPlayer *plr = audioPlayer_Media(media, (iMediaId){ audio_MediaType, id });
@@ -3374,7 +3422,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
3374 return iTrue; 3422 return iTrue;
3375 } 3423 }
3376 else if (equal_Command(cmd, "document.reload") && document_Command(cmd) == d) { 3424 else if (equal_Command(cmd, "document.reload") && document_Command(cmd) == d) {
3377 d->initNormScrollY = normScrollPos_DocumentWidget_(d); 3425 d->initNormScrollY = normScrollPos_DocumentView_(&d->view);
3378 if (equalCase_Rangecc(urlScheme_String(d->mod.url), "titan")) { 3426 if (equalCase_Rangecc(urlScheme_String(d->mod.url), "titan")) {
3379 /* Reopen so the Upload dialog gets shown. */ 3427 /* Reopen so the Upload dialog gets shown. */
3380 postCommandf_App("open url:%s", cstr_String(d->mod.url)); 3428 postCommandf_App("open url:%s", cstr_String(d->mod.url));
@@ -3391,13 +3439,13 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
3391 if (d->flags & showLinkNumbers_DocumentWidgetFlag && 3439 if (d->flags & showLinkNumbers_DocumentWidgetFlag &&
3392 d->ordinalMode == homeRow_DocumentLinkOrdinalMode) { 3440 d->ordinalMode == homeRow_DocumentLinkOrdinalMode) {
3393 const size_t numKeys = iElemCount(homeRowKeys_); 3441 const size_t numKeys = iElemCount(homeRowKeys_);
3394 const iGmRun *last = lastVisibleLink_DocumentWidget_(d); 3442 const iGmRun *last = lastVisibleLink_DocumentView_(&d->view);
3395 if (!last) { 3443 if (!last) {
3396 d->ordinalBase = 0; 3444 d->ordinalBase = 0;
3397 } 3445 }
3398 else { 3446 else {
3399 d->ordinalBase += numKeys; 3447 d->ordinalBase += numKeys;
3400 if (visibleLinkOrdinal_DocumentWidget_(d, last->linkId) < d->ordinalBase) { 3448 if (visibleLinkOrdinal_DocumentView_(&d->view, last->linkId) < d->ordinalBase) {
3401 d->ordinalBase = 0; 3449 d->ordinalBase = 0;
3402 } 3450 }
3403 } 3451 }
@@ -3417,25 +3465,11 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
3417 iChangeFlags(d->flags, newTabViaHomeKeys_DocumentWidgetFlag, 3465 iChangeFlags(d->flags, newTabViaHomeKeys_DocumentWidgetFlag,
3418 argLabel_Command(cmd, "newtab") != 0); 3466 argLabel_Command(cmd, "newtab") != 0);
3419 } 3467 }
3420 invalidateVisibleLinks_DocumentWidget_(d); 3468 invalidateVisibleLinks_DocumentView_(&d->view);
3421 refresh_Widget(d); 3469 refresh_Widget(d);
3422 return iTrue; 3470 return iTrue;
3423 } 3471 }
3424 else if (equal_Command(cmd, "navigate.back") && document_App() == d) { 3472 else if (equal_Command(cmd, "navigate.back") && document_App() == d) {
3425#if 0
3426 if (isPortraitPhone_App()) {
3427 if (d->flags & openedFromSidebar_DocumentWidgetFlag &&
3428 !isVisible_Widget(findWidget_App("sidebar"))) {
3429 postCommand_App("sidebar.toggle");
3430 showToolbar_Root(get_Root(), iTrue);
3431#if defined (iPlatformAppleMobile)
3432 playHapticEffect_iOS(gentleTap_HapticEffect);
3433#endif
3434 return iTrue;
3435 }
3436 d->flags &= ~openedFromSidebar_DocumentWidgetFlag;
3437 }
3438#endif
3439 if (d->request) { 3473 if (d->request) {
3440 postCommandf_Root(w->root, 3474 postCommandf_Root(w->root,
3441 "document.request.cancelled doc:%p url:%s", d, cstr_String(d->mod.url)); 3475 "document.request.cancelled doc:%p url:%s", d, cstr_String(d->mod.url));
@@ -3472,8 +3506,8 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
3472 return iTrue; 3506 return iTrue;
3473 } 3507 }
3474 else if (equalWidget_Command(cmd, w, "scroll.moved")) { 3508 else if (equalWidget_Command(cmd, w, "scroll.moved")) {
3475 init_Anim(&d->scrollY.pos, arg_Command(cmd)); 3509 init_Anim(&d->view.scrollY.pos, arg_Command(cmd));
3476 updateVisible_DocumentWidget_(d); 3510 updateVisible_DocumentView_(&d->view);
3477 return iTrue; 3511 return iTrue;
3478 } 3512 }
3479 else if (equal_Command(cmd, "scroll.page") && document_App() == d) { 3513 else if (equal_Command(cmd, "scroll.page") && document_App() == d) {
@@ -3484,25 +3518,26 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
3484 return iTrue; 3518 return iTrue;
3485 } 3519 }
3486 const float amount = argLabel_Command(cmd, "full") != 0 ? 1.0f : 0.5f; 3520 const float amount = argLabel_Command(cmd, "full") != 0 ? 1.0f : 0.5f;
3487 smoothScroll_DocumentWidget_(d, 3521 smoothScroll_DocumentView_(&d->view,
3488 dir * amount * height_Rect(documentBounds_DocumentWidget_(d)), 3522 dir * amount *
3489 smoothDuration_DocumentWidget_(keyboard_ScrollType)); 3523 height_Rect(documentBounds_DocumentView_(&d->view)),
3524 smoothDuration_DocumentWidget_(keyboard_ScrollType));
3490 return iTrue; 3525 return iTrue;
3491 } 3526 }
3492 else if (equal_Command(cmd, "scroll.top") && document_App() == d) { 3527 else if (equal_Command(cmd, "scroll.top") && document_App() == d) {
3493 init_Anim(&d->scrollY.pos, 0); 3528 init_Anim(&d->view.scrollY.pos, 0);
3494 invalidate_VisBuf(d->visBuf); 3529 invalidate_VisBuf(d->view.visBuf);
3495 clampScroll_DocumentWidget_(d); 3530 clampScroll_DocumentView_(&d->view);
3496 updateVisible_DocumentWidget_(d); 3531 updateVisible_DocumentView_(&d->view);
3497 refresh_Widget(w); 3532 refresh_Widget(w);
3498 return iTrue; 3533 return iTrue;
3499 } 3534 }
3500 else if (equal_Command(cmd, "scroll.bottom") && document_App() == d) { 3535 else if (equal_Command(cmd, "scroll.bottom") && document_App() == d) {
3501 updateScrollMax_DocumentWidget_(d); /* scrollY.max might not be fully updated */ 3536 updateScrollMax_DocumentView_(&d->view); /* scrollY.max might not be fully updated */
3502 init_Anim(&d->scrollY.pos, d->scrollY.max); 3537 init_Anim(&d->view.scrollY.pos, d->view.scrollY.max);
3503 invalidate_VisBuf(d->visBuf); 3538 invalidate_VisBuf(d->view.visBuf);
3504 clampScroll_DocumentWidget_(d); 3539 clampScroll_DocumentView_(&d->view);
3505 updateVisible_DocumentWidget_(d); 3540 updateVisible_DocumentView_(&d->view);
3506 refresh_Widget(w); 3541 refresh_Widget(w);
3507 return iTrue; 3542 return iTrue;
3508 } 3543 }
@@ -3513,9 +3548,9 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
3513 fetchNextUnfetchedImage_DocumentWidget_(d)) { 3548 fetchNextUnfetchedImage_DocumentWidget_(d)) {
3514 return iTrue; 3549 return iTrue;
3515 } 3550 }
3516 smoothScroll_DocumentWidget_(d, 3551 smoothScroll_DocumentView_(&d->view,
3517 3 * lineHeight_Text(paragraph_FontId) * dir, 3552 3 * lineHeight_Text(paragraph_FontId) * dir,
3518 smoothDuration_DocumentWidget_(keyboard_ScrollType)); 3553 smoothDuration_DocumentWidget_(keyboard_ScrollType));
3519 return iTrue; 3554 return iTrue;
3520 } 3555 }
3521 else if (equal_Command(cmd, "document.goto") && document_App() == d) { 3556 else if (equal_Command(cmd, "document.goto") && document_App() == d) {
@@ -3526,13 +3561,13 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
3526 setCStr_String(&d->pendingGotoHeading, heading); 3561 setCStr_String(&d->pendingGotoHeading, heading);
3527 return iTrue; 3562 return iTrue;
3528 } 3563 }
3529 scrollToHeading_DocumentWidget_(d, heading); 3564 scrollToHeading_DocumentView_(&d->view, heading);
3530 return iTrue; 3565 return iTrue;
3531 } 3566 }
3532 const char *loc = pointerLabel_Command(cmd, "loc"); 3567 const char *loc = pointerLabel_Command(cmd, "loc");
3533 const iGmRun *run = findRunAtLoc_GmDocument(d->doc, loc); 3568 const iGmRun *run = findRunAtLoc_GmDocument(d->view.doc, loc);
3534 if (run) { 3569 if (run) {
3535 scrollTo_DocumentWidget_(d, run->visBounds.pos.y, iFalse); 3570 scrollTo_DocumentView_(&d->view, run->visBounds.pos.y, iFalse);
3536 } 3571 }
3537 return iTrue; 3572 return iTrue;
3538 } 3573 }
@@ -3547,24 +3582,24 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
3547 } 3582 }
3548 else { 3583 else {
3549 const iBool wrap = d->foundMark.start != NULL; 3584 const iBool wrap = d->foundMark.start != NULL;
3550 d->foundMark = finder(d->doc, text_InputWidget(find), dir > 0 ? d->foundMark.end 3585 d->foundMark = finder(d->view.doc, text_InputWidget(find), dir > 0 ? d->foundMark.end
3551 : d->foundMark.start); 3586 : d->foundMark.start);
3552 if (!d->foundMark.start && wrap) { 3587 if (!d->foundMark.start && wrap) {
3553 /* Wrap around. */ 3588 /* Wrap around. */
3554 d->foundMark = finder(d->doc, text_InputWidget(find), NULL); 3589 d->foundMark = finder(d->view.doc, text_InputWidget(find), NULL);
3555 } 3590 }
3556 if (d->foundMark.start) { 3591 if (d->foundMark.start) {
3557 const iGmRun *found; 3592 const iGmRun *found;
3558 if ((found = findRunAtLoc_GmDocument(d->doc, d->foundMark.start)) != NULL) { 3593 if ((found = findRunAtLoc_GmDocument(d->view.doc, d->foundMark.start)) != NULL) {
3559 scrollTo_DocumentWidget_(d, mid_Rect(found->bounds).y, iTrue); 3594 scrollTo_DocumentView_(&d->view, mid_Rect(found->bounds).y, iTrue);
3560 } 3595 }
3561 } 3596 }
3562 } 3597 }
3563 if (flags_Widget(w) & touchDrag_WidgetFlag) { 3598 if (flags_Widget(w) & touchDrag_WidgetFlag) {
3564 postCommand_Root(w->root, "document.select arg:0"); /* we can't handle both at the same time */ 3599 postCommand_Root(w->root, "document.select arg:0"); /* we can't handle both at the same time */
3565 } 3600 }
3566 invalidateWideRunsWithNonzeroOffset_DocumentWidget_(d); /* markers don't support offsets */ 3601 invalidateWideRunsWithNonzeroOffset_DocumentView_(&d->view); /* markers don't support offsets */
3567 resetWideRuns_DocumentWidget_(d); 3602 resetWideRuns_DocumentView_(&d->view);
3568 refresh_Widget(w); 3603 refresh_Widget(w);
3569 return iTrue; 3604 return iTrue;
3570 } 3605 }
@@ -3577,13 +3612,13 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
3577 } 3612 }
3578 else if (equal_Command(cmd, "bookmark.links") && document_App() == d) { 3613 else if (equal_Command(cmd, "bookmark.links") && document_App() == d) {
3579 iPtrArray *links = collectNew_PtrArray(); 3614 iPtrArray *links = collectNew_PtrArray();
3580 render_GmDocument(d->doc, (iRangei){ 0, size_GmDocument(d->doc).y }, addAllLinks_, links); 3615 render_GmDocument(d->view.doc, (iRangei){ 0, size_GmDocument(d->view.doc).y }, addAllLinks_, links);
3581 /* Find links that aren't already bookmarked. */ 3616 /* Find links that aren't already bookmarked. */
3582 iForEach(PtrArray, i, links) { 3617 iForEach(PtrArray, i, links) {
3583 const iGmRun *run = i.ptr; 3618 const iGmRun *run = i.ptr;
3584 uint32_t bmid; 3619 uint32_t bmid;
3585 if ((bmid = findUrl_Bookmarks(bookmarks_App(), 3620 if ((bmid = findUrl_Bookmarks(bookmarks_App(),
3586 linkUrl_GmDocument(d->doc, run->linkId))) != 0) { 3621 linkUrl_GmDocument(d->view.doc, run->linkId))) != 0) {
3587 const iBookmark *bm = get_Bookmarks(bookmarks_App(), bmid); 3622 const iBookmark *bm = get_Bookmarks(bookmarks_App(), bmid);
3588 /* We can import local copies of remote bookmarks. */ 3623 /* We can import local copies of remote bookmarks. */
3589 if (~bm->flags & remote_BookmarkFlag) { 3624 if (~bm->flags & remote_BookmarkFlag) {
@@ -3610,7 +3645,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
3610 iConstForEach(PtrArray, j, links) { 3645 iConstForEach(PtrArray, j, links) {
3611 const iGmRun *run = j.ptr; 3646 const iGmRun *run = j.ptr;
3612 add_Bookmarks(bookmarks_App(), 3647 add_Bookmarks(bookmarks_App(),
3613 linkUrl_GmDocument(d->doc, run->linkId), 3648 linkUrl_GmDocument(d->view.doc, run->linkId),
3614 collect_String(newRange_String(run->text)), 3649 collect_String(newRange_String(run->text)),
3615 NULL, 3650 NULL,
3616 0x1f588 /* pin */); 3651 0x1f588 /* pin */);
@@ -3625,7 +3660,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
3625 return iTrue; 3660 return iTrue;
3626 } 3661 }
3627 else if (equalWidget_Command(cmd, w, "menu.closed")) { 3662 else if (equalWidget_Command(cmd, w, "menu.closed")) {
3628 updateHover_DocumentWidget_(d, mouseCoord_Window(get_Window(), 0)); 3663 updateHover_DocumentView_(&d->view, mouseCoord_Window(get_Window(), 0));
3629 } 3664 }
3630 else if (equal_Command(cmd, "document.autoreload")) { 3665 else if (equal_Command(cmd, "document.autoreload")) {
3631 if (d->mod.reloadInterval) { 3666 if (d->mod.reloadInterval) {
@@ -3695,14 +3730,14 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
3695 return iFalse; 3730 return iFalse;
3696} 3731}
3697 3732
3698static iRect runRect_DocumentWidget_(const iDocumentWidget *d, const iGmRun *run) { 3733static iRect runRect_DocumentView_(const iDocumentView *d, const iGmRun *run) {
3699 const iRect docBounds = documentBounds_DocumentWidget_(d); 3734 const iRect docBounds = documentBounds_DocumentView_(d);
3700 return moved_Rect(run->bounds, addY_I2(topLeft_Rect(docBounds), viewPos_DocumentWidget_(d))); 3735 return moved_Rect(run->bounds, addY_I2(topLeft_Rect(docBounds), viewPos_DocumentView_(d)));
3701} 3736}
3702 3737
3703static void setGrabbedPlayer_DocumentWidget_(iDocumentWidget *d, const iGmRun *run) { 3738static void setGrabbedPlayer_DocumentWidget_(iDocumentWidget *d, const iGmRun *run) {
3704 if (run && run->mediaType == audio_MediaType) { 3739 if (run && run->mediaType == audio_MediaType) {
3705 iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run)); 3740 iPlayer *plr = audioPlayer_Media(media_GmDocument(d->view.doc), mediaId_GmRun(run));
3706 setFlags_Player(plr, volumeGrabbed_PlayerFlag, iTrue); 3741 setFlags_Player(plr, volumeGrabbed_PlayerFlag, iTrue);
3707 d->grabbedStartVolume = volume_Player(plr); 3742 d->grabbedStartVolume = volume_Player(plr);
3708 d->grabbedPlayer = run; 3743 d->grabbedPlayer = run;
@@ -3710,7 +3745,7 @@ static void setGrabbedPlayer_DocumentWidget_(iDocumentWidget *d, const iGmRun *r
3710 } 3745 }
3711 else if (d->grabbedPlayer) { 3746 else if (d->grabbedPlayer) {
3712 setFlags_Player( 3747 setFlags_Player(
3713 audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(d->grabbedPlayer)), 3748 audioPlayer_Media(media_GmDocument(d->view.doc), mediaId_GmRun(d->grabbedPlayer)),
3714 volumeGrabbed_PlayerFlag, 3749 volumeGrabbed_PlayerFlag,
3715 iFalse); 3750 iFalse);
3716 d->grabbedPlayer = NULL; 3751 d->grabbedPlayer = NULL;
@@ -3736,14 +3771,14 @@ static iBool processMediaEvents_DocumentWidget_(iDocumentWidget *d, const SDL_Ev
3736 return iFalse; 3771 return iFalse;
3737 } 3772 }
3738 const iInt2 mouse = init_I2(ev->button.x, ev->button.y); 3773 const iInt2 mouse = init_I2(ev->button.x, ev->button.y);
3739 iConstForEach(PtrArray, i, &d->visibleMedia) { 3774 iConstForEach(PtrArray, i, &d->view.visibleMedia) {
3740 const iGmRun *run = i.ptr; 3775 const iGmRun *run = i.ptr;
3741 if (run->mediaType != audio_MediaType) { 3776 if (run->mediaType != audio_MediaType) {
3742 continue; 3777 continue;
3743 } 3778 }
3744 /* TODO: move this to mediaui.c */ 3779 /* TODO: move this to mediaui.c */
3745 const iRect rect = runRect_DocumentWidget_(d, run); 3780 const iRect rect = runRect_DocumentView_(&d->view, run);
3746 iPlayer * plr = audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run)); 3781 iPlayer * plr = audioPlayer_Media(media_GmDocument(d->view.doc), mediaId_GmRun(run));
3747 if (contains_Rect(rect, mouse)) { 3782 if (contains_Rect(rect, mouse)) {
3748 iPlayerUI ui; 3783 iPlayerUI ui;
3749 init_PlayerUI(&ui, plr, rect); 3784 init_PlayerUI(&ui, plr, rect);
@@ -3869,20 +3904,20 @@ static iChar linkOrdinalChar_DocumentWidget_(const iDocumentWidget *d, size_t or
3869 3904
3870static void beginMarkingSelection_DocumentWidget_(iDocumentWidget *d, iInt2 pos) { 3905static void beginMarkingSelection_DocumentWidget_(iDocumentWidget *d, iInt2 pos) {
3871 setFocus_Widget(NULL); /* TODO: Focus this document? */ 3906 setFocus_Widget(NULL); /* TODO: Focus this document? */
3872 invalidateWideRunsWithNonzeroOffset_DocumentWidget_(d); 3907 invalidateWideRunsWithNonzeroOffset_DocumentView_(&d->view);
3873 resetWideRuns_DocumentWidget_(d); /* Selections don't support horizontal scrolling. */ 3908 resetWideRuns_DocumentView_(&d->view); /* Selections don't support horizontal scrolling. */
3874 iChangeFlags(d->flags, selecting_DocumentWidgetFlag, iTrue); 3909 iChangeFlags(d->flags, selecting_DocumentWidgetFlag, iTrue);
3875 d->initialSelectMark = d->selectMark = sourceLoc_DocumentWidget_(d, pos); 3910 d->initialSelectMark = d->selectMark = sourceLoc_DocumentView_(&d->view, pos);
3876 refresh_Widget(as_Widget(d)); 3911 refresh_Widget(as_Widget(d));
3877} 3912}
3878 3913
3879static void interactingWithLink_DocumentWidget_(iDocumentWidget *d, iGmLinkId id) { 3914static void interactingWithLink_DocumentWidget_(iDocumentWidget *d, iGmLinkId id) {
3880 iRangecc loc = linkUrlRange_GmDocument(d->doc, id); 3915 iRangecc loc = linkUrlRange_GmDocument(d->view.doc, id);
3881 if (!loc.start) { 3916 if (!loc.start) {
3882 clear_String(&d->linePrecedingLink); 3917 clear_String(&d->linePrecedingLink);
3883 return; 3918 return;
3884 } 3919 }
3885 const char *start = range_String(source_GmDocument(d->doc)).start; 3920 const char *start = range_String(source_GmDocument(d->view.doc)).start;
3886 /* Find the preceding line. This is offered as a prefill option for a possible input query. */ 3921 /* Find the preceding line. This is offered as a prefill option for a possible input query. */
3887 while (loc.start > start && *loc.start != '\n') { 3922 while (loc.start > start && *loc.start != '\n') {
3888 loc.start--; 3923 loc.start--;
@@ -3987,11 +4022,12 @@ static iBool handleWheelSwipe_DocumentWidget_(iDocumentWidget *d, const SDL_Mous
3987} 4022}
3988 4023
3989static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *ev) { 4024static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *ev) {
3990 iWidget *w = as_Widget(d); 4025 iWidget *w = as_Widget(d);
4026 iDocumentView *view = &d->view;
3991 if (isMetricsChange_UserEvent(ev)) { 4027 if (isMetricsChange_UserEvent(ev)) {
3992 updateSize_DocumentWidget(d); 4028 updateSize_DocumentWidget(d);
3993 } 4029 }
3994 else if (processEvent_SmoothScroll(&d->scrollY, ev)) { 4030 else if (processEvent_SmoothScroll(&d->view.scrollY, ev)) {
3995 return iTrue; 4031 return iTrue;
3996 } 4032 }
3997 else if (ev->type == SDL_USEREVENT && ev->user.code == command_UserEventCode) { 4033 else if (ev->type == SDL_USEREVENT && ev->user.code == command_UserEventCode) {
@@ -4010,28 +4046,28 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
4010 if ((d->flags & showLinkNumbers_DocumentWidgetFlag) && 4046 if ((d->flags & showLinkNumbers_DocumentWidgetFlag) &&
4011 ((key >= '1' && key <= '9') || (key >= 'a' && key <= 'z'))) { 4047 ((key >= '1' && key <= '9') || (key >= 'a' && key <= 'z'))) {
4012 const size_t ord = linkOrdinalFromKey_DocumentWidget_(d, key) + d->ordinalBase; 4048 const size_t ord = linkOrdinalFromKey_DocumentWidget_(d, key) + d->ordinalBase;
4013 iConstForEach(PtrArray, i, &d->visibleLinks) { 4049 iConstForEach(PtrArray, i, &d->view.visibleLinks) {
4014 if (ord == iInvalidPos) break; 4050 if (ord == iInvalidPos) break;
4015 const iGmRun *run = i.ptr; 4051 const iGmRun *run = i.ptr;
4016 if (run->flags & decoration_GmRunFlag && 4052 if (run->flags & decoration_GmRunFlag &&
4017 visibleLinkOrdinal_DocumentWidget_(d, run->linkId) == ord) { 4053 visibleLinkOrdinal_DocumentView_(view, run->linkId) == ord) {
4018 if (d->flags & setHoverViaKeys_DocumentWidgetFlag) { 4054 if (d->flags & setHoverViaKeys_DocumentWidgetFlag) {
4019 d->hoverLink = run; 4055 view->hoverLink = run;
4020 } 4056 }
4021 else { 4057 else {
4022 postCommandf_Root(w->root, 4058 postCommandf_Root(
4023 "open newtab:%d url:%s", 4059 w->root,
4024 (isPinned_DocumentWidget_(d) ? otherRoot_OpenTabFlag : 0) ^ 4060 "open newtab:%d url:%s",
4025 (d->ordinalMode == 4061 (isPinned_DocumentWidget_(d) ? otherRoot_OpenTabFlag : 0) ^
4026 numbersAndAlphabet_DocumentLinkOrdinalMode 4062 (d->ordinalMode == numbersAndAlphabet_DocumentLinkOrdinalMode
4027 ? openTabMode_Sym(modState_Keys()) 4063 ? openTabMode_Sym(modState_Keys())
4028 : (d->flags & newTabViaHomeKeys_DocumentWidgetFlag ? 1 : 0)), 4064 : (d->flags & newTabViaHomeKeys_DocumentWidgetFlag ? 1 : 0)),
4029 cstr_String(absoluteUrl_String( 4065 cstr_String(absoluteUrl_String(
4030 d->mod.url, linkUrl_GmDocument(d->doc, run->linkId)))); 4066 d->mod.url, linkUrl_GmDocument(view->doc, run->linkId))));
4031 interactingWithLink_DocumentWidget_(d, run->linkId); 4067 interactingWithLink_DocumentWidget_(d, run->linkId);
4032 } 4068 }
4033 setLinkNumberMode_DocumentWidget_(d, iFalse); 4069 setLinkNumberMode_DocumentWidget_(d, iFalse);
4034 invalidateVisibleLinks_DocumentWidget_(d); 4070 invalidateVisibleLinks_DocumentView_(view);
4035 refresh_Widget(d); 4071 refresh_Widget(d);
4036 return iTrue; 4072 return iTrue;
4037 } 4073 }
@@ -4041,7 +4077,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
4041 case SDLK_ESCAPE: 4077 case SDLK_ESCAPE:
4042 if (d->flags & showLinkNumbers_DocumentWidgetFlag && document_App() == d) { 4078 if (d->flags & showLinkNumbers_DocumentWidgetFlag && document_App() == d) {
4043 setLinkNumberMode_DocumentWidget_(d, iFalse); 4079 setLinkNumberMode_DocumentWidget_(d, iFalse);
4044 invalidateVisibleLinks_DocumentWidget_(d); 4080 invalidateVisibleLinks_DocumentView_(view);
4045 refresh_Widget(d); 4081 refresh_Widget(d);
4046 return iTrue; 4082 return iTrue;
4047 } 4083 }
@@ -4053,7 +4089,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
4053 for (size_t i = 0; i < 64; ++i) { 4089 for (size_t i = 0; i < 64; ++i) {
4054 setByte_Block(seed, i, iRandom(0, 256)); 4090 setByte_Block(seed, i, iRandom(0, 256));
4055 } 4091 }
4056 setThemeSeed_GmDocument(d->doc, seed); 4092 setThemeSeed_GmDocument(view->doc, seed);
4057 delete_Block(seed); 4093 delete_Block(seed);
4058 invalidate_DocumentWidget_(d); 4094 invalidate_DocumentWidget_(d);
4059 refresh_Widget(w); 4095 refresh_Widget(w);
@@ -4095,9 +4131,9 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
4095 const iInt2 mouseCoord = coord_MouseWheelEvent(&ev->wheel); 4131 const iInt2 mouseCoord = coord_MouseWheelEvent(&ev->wheel);
4096 if (isPerPixel_MouseWheelEvent(&ev->wheel)) { 4132 if (isPerPixel_MouseWheelEvent(&ev->wheel)) {
4097 const iInt2 wheel = init_I2(ev->wheel.x, ev->wheel.y); 4133 const iInt2 wheel = init_I2(ev->wheel.x, ev->wheel.y);
4098 stop_Anim(&d->scrollY.pos); 4134 stop_Anim(&d->view.scrollY.pos);
4099 immediateScroll_DocumentWidget_(d, -wheel.y); 4135 immediateScroll_DocumentView_(view, -wheel.y);
4100 if (!scrollWideBlock_DocumentWidget_(d, mouseCoord, -wheel.x, 0) && 4136 if (!scrollWideBlock_DocumentView_(view, mouseCoord, -wheel.x, 0) &&
4101 wheel.x) { 4137 wheel.x) {
4102 handleWheelSwipe_DocumentWidget_(d, &ev->wheel); 4138 handleWheelSwipe_DocumentWidget_(d, &ev->wheel);
4103 } 4139 }
@@ -4109,16 +4145,11 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
4109 postCommandf_App("zoom.delta arg:%d", amount > 0 ? 10 : -10); 4145 postCommandf_App("zoom.delta arg:%d", amount > 0 ? 10 : -10);
4110 return iTrue; 4146 return iTrue;
4111 } 4147 }
4112 smoothScroll_DocumentWidget_( 4148 smoothScroll_DocumentView_(view,
4113 d, 4149 -3 * amount * lineHeight_Text(paragraph_FontId),
4114 -3 * amount * lineHeight_Text(paragraph_FontId), 4150 smoothDuration_DocumentWidget_(mouse_ScrollType));
4115 smoothDuration_DocumentWidget_(mouse_ScrollType)); 4151 scrollWideBlock_DocumentView_(
4116 /* accelerated speed for repeated wheelings */ 4152 view, mouseCoord, -3 * ev->wheel.x * lineHeight_Text(paragraph_FontId), 167);
4117// * (!isFinished_SmoothScroll(&d->scrollY) && pos_Anim(&d->scrollY.pos) < 0.25f
4118// ? 0.5f
4119// : 1.0f));
4120 scrollWideBlock_DocumentWidget_(
4121 d, mouseCoord, -3 * ev->wheel.x * lineHeight_Text(paragraph_FontId), 167);
4122 } 4153 }
4123 iChangeFlags(d->flags, noHoverWhileScrolling_DocumentWidgetFlag, iTrue); 4154 iChangeFlags(d->flags, noHoverWhileScrolling_DocumentWidgetFlag, iTrue);
4124 return iTrue; 4155 return iTrue;
@@ -4137,10 +4168,10 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
4137 } 4168 }
4138#endif 4169#endif
4139 else { 4170 else {
4140 if (value_Anim(&d->altTextOpacity) < 0.833f) { 4171 if (value_Anim(&view->altTextOpacity) < 0.833f) {
4141 setValue_Anim(&d->altTextOpacity, 0, 0); /* keep it hidden while moving */ 4172 setValue_Anim(&view->altTextOpacity, 0, 0); /* keep it hidden while moving */
4142 } 4173 }
4143 updateHover_DocumentWidget_(d, mpos); 4174 updateHover_DocumentView_(view, mpos);
4144 } 4175 }
4145 } 4176 }
4146 if (ev->type == SDL_USEREVENT && ev->user.code == widgetTapBegins_UserEventCode) { 4177 if (ev->type == SDL_USEREVENT && ev->user.code == widgetTapBegins_UserEventCode) {
@@ -4156,18 +4187,18 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
4156 postCommand_Root(w->root, "navigate.forward"); 4187 postCommand_Root(w->root, "navigate.forward");
4157 return iTrue; 4188 return iTrue;
4158 } 4189 }
4159 if (ev->button.button == SDL_BUTTON_MIDDLE && d->hoverLink) { 4190 if (ev->button.button == SDL_BUTTON_MIDDLE && view->hoverLink) {
4160 interactingWithLink_DocumentWidget_(d, d->hoverLink->linkId); 4191 interactingWithLink_DocumentWidget_(d, view->hoverLink->linkId);
4161 postCommandf_Root(w->root, "open newtab:%d url:%s", 4192 postCommandf_Root(w->root, "open newtab:%d url:%s",
4162 (isPinned_DocumentWidget_(d) ? otherRoot_OpenTabFlag : 0) | 4193 (isPinned_DocumentWidget_(d) ? otherRoot_OpenTabFlag : 0) |
4163 (modState_Keys() & KMOD_SHIFT ? new_OpenTabFlag : newBackground_OpenTabFlag), 4194 (modState_Keys() & KMOD_SHIFT ? new_OpenTabFlag : newBackground_OpenTabFlag),
4164 cstr_String(linkUrl_GmDocument(d->doc, d->hoverLink->linkId))); 4195 cstr_String(linkUrl_GmDocument(view->doc, view->hoverLink->linkId)));
4165 return iTrue; 4196 return iTrue;
4166 } 4197 }
4167 if (ev->button.button == SDL_BUTTON_RIGHT && 4198 if (ev->button.button == SDL_BUTTON_RIGHT &&
4168 contains_Widget(w, init_I2(ev->button.x, ev->button.y))) { 4199 contains_Widget(w, init_I2(ev->button.x, ev->button.y))) {
4169 if (!isVisible_Widget(d->menu)) { 4200 if (!isVisible_Widget(d->menu)) {
4170 d->contextLink = d->hoverLink; 4201 d->contextLink = view->hoverLink;
4171 d->contextPos = init_I2(ev->button.x, ev->button.y); 4202 d->contextPos = init_I2(ev->button.x, ev->button.y);
4172 if (d->menu) { 4203 if (d->menu) {
4173 destroy_Widget(d->menu); 4204 destroy_Widget(d->menu);
@@ -4179,7 +4210,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
4179 if (d->contextLink) { 4210 if (d->contextLink) {
4180 /* Context menu for a link. */ 4211 /* Context menu for a link. */
4181 interactingWithLink_DocumentWidget_(d, d->contextLink->linkId); /* perhaps will be triggered */ 4212 interactingWithLink_DocumentWidget_(d, d->contextLink->linkId); /* perhaps will be triggered */
4182 const iString *linkUrl = linkUrl_GmDocument(d->doc, d->contextLink->linkId); 4213 const iString *linkUrl = linkUrl_GmDocument(view->doc, d->contextLink->linkId);
4183// const int linkFlags = linkFlags_GmDocument(d->doc, d->contextLink->linkId); 4214// const int linkFlags = linkFlags_GmDocument(d->doc, d->contextLink->linkId);
4184 const iRangecc scheme = urlScheme_String(linkUrl); 4215 const iRangecc scheme = urlScheme_String(linkUrl);
4185 const iBool isGemini = equalCase_Rangecc(scheme, "gemini"); 4216 const iBool isGemini = equalCase_Rangecc(scheme, "gemini");
@@ -4249,7 +4280,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
4249 2); 4280 2);
4250 } 4281 }
4251 iString *linkLabel = collectNewRange_String( 4282 iString *linkLabel = collectNewRange_String(
4252 linkLabel_GmDocument(d->doc, d->contextLink->linkId)); 4283 linkLabel_GmDocument(view->doc, d->contextLink->linkId));
4253 urlEncodeSpaces_String(linkLabel); 4284 urlEncodeSpaces_String(linkLabel);
4254 pushBackN_Array(&items, 4285 pushBackN_Array(&items,
4255 (iMenuItem[]){ { "---" }, 4286 (iMenuItem[]){ { "---" },
@@ -4370,7 +4401,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
4370 /* Enable hover state now that scrolling has surely finished. */ 4401 /* Enable hover state now that scrolling has surely finished. */
4371 if (d->flags & noHoverWhileScrolling_DocumentWidgetFlag) { 4402 if (d->flags & noHoverWhileScrolling_DocumentWidgetFlag) {
4372 d->flags &= ~noHoverWhileScrolling_DocumentWidgetFlag; 4403 d->flags &= ~noHoverWhileScrolling_DocumentWidgetFlag;
4373 updateHover_DocumentWidget_(d, mouseCoord_Window(get_Window(), ev->button.which)); 4404 updateHover_DocumentView_(view, mouseCoord_Window(get_Window(), ev->button.which));
4374 } 4405 }
4375 if (~flags_Widget(w) & touchDrag_WidgetFlag) { 4406 if (~flags_Widget(w) & touchDrag_WidgetFlag) {
4376 iChangeFlags(d->flags, selecting_DocumentWidgetFlag, iFalse); 4407 iChangeFlags(d->flags, selecting_DocumentWidgetFlag, iFalse);
@@ -4381,7 +4412,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
4381 beginMarkingSelection_DocumentWidget_(d, d->click.startPos); 4412 beginMarkingSelection_DocumentWidget_(d, d->click.startPos);
4382 extendRange_Rangecc( 4413 extendRange_Rangecc(
4383 &d->selectMark, 4414 &d->selectMark,
4384 range_String(source_GmDocument(d->doc)), 4415 range_String(source_GmDocument(view->doc)),
4385 bothStartAndEnd_RangeExtension | 4416 bothStartAndEnd_RangeExtension |
4386 (d->click.count == 2 ? word_RangeExtension : line_RangeExtension)); 4417 (d->click.count == 2 ? word_RangeExtension : line_RangeExtension));
4387 d->initialSelectMark = d->selectMark; 4418 d->initialSelectMark = d->selectMark;
@@ -4395,24 +4426,24 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
4395 case drag_ClickResult: { 4426 case drag_ClickResult: {
4396 if (d->grabbedPlayer) { 4427 if (d->grabbedPlayer) {
4397 iPlayer *plr = 4428 iPlayer *plr =
4398 audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(d->grabbedPlayer)); 4429 audioPlayer_Media(media_GmDocument(view->doc), mediaId_GmRun(d->grabbedPlayer));
4399 iPlayerUI ui; 4430 iPlayerUI ui;
4400 init_PlayerUI(&ui, plr, runRect_DocumentWidget_(d, d->grabbedPlayer)); 4431 init_PlayerUI(&ui, plr, runRect_DocumentView_(view, d->grabbedPlayer));
4401 float off = (float) delta_Click(&d->click).x / (float) width_Rect(ui.volumeSlider); 4432 float off = (float) delta_Click(&d->click).x / (float) width_Rect(ui.volumeSlider);
4402 setVolume_Player(plr, d->grabbedStartVolume + off); 4433 setVolume_Player(plr, d->grabbedStartVolume + off);
4403 refresh_Widget(w); 4434 refresh_Widget(w);
4404 return iTrue; 4435 return iTrue;
4405 } 4436 }
4406 /* Fold/unfold a preformatted block. */ 4437 /* Fold/unfold a preformatted block. */
4407 if (~d->flags & selecting_DocumentWidgetFlag && d->hoverPre && 4438 if (~d->flags & selecting_DocumentWidgetFlag && view->hoverPre &&
4408 preIsFolded_GmDocument(d->doc, preId_GmRun(d->hoverPre))) { 4439 preIsFolded_GmDocument(view->doc, preId_GmRun(view->hoverPre))) {
4409 return iTrue; 4440 return iTrue;
4410 } 4441 }
4411 /* Begin selecting a range of text. */ 4442 /* Begin selecting a range of text. */
4412 if (~d->flags & selecting_DocumentWidgetFlag) { 4443 if (~d->flags & selecting_DocumentWidgetFlag) {
4413 beginMarkingSelection_DocumentWidget_(d, d->click.startPos); 4444 beginMarkingSelection_DocumentWidget_(d, d->click.startPos);
4414 } 4445 }
4415 iRangecc loc = sourceLoc_DocumentWidget_(d, pos_Click(&d->click)); 4446 iRangecc loc = sourceLoc_DocumentView_(view, pos_Click(&d->click));
4416 if (d->selectMark.start == NULL) { 4447 if (d->selectMark.start == NULL) {
4417 d->selectMark = loc; 4448 d->selectMark = loc;
4418 } 4449 }
@@ -4423,7 +4454,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
4423 movingSelectMarkEnd_DocumentWidgetFlag))) { 4454 movingSelectMarkEnd_DocumentWidgetFlag))) {
4424 const iRangecc mark = selectMark_DocumentWidget_(d); 4455 const iRangecc mark = selectMark_DocumentWidget_(d);
4425 const char * midMark = mark.start + size_Range(&mark) / 2; 4456 const char * midMark = mark.start + size_Range(&mark) / 2;
4426 const iRangecc loc = sourceLoc_DocumentWidget_(d, pos_Click(&d->click)); 4457 const iRangecc loc = sourceLoc_DocumentView_(view, pos_Click(&d->click));
4427 const iBool isCloserToStart = d->selectMark.start > d->selectMark.end ? 4458 const iBool isCloserToStart = d->selectMark.start > d->selectMark.end ?
4428 (loc.start > midMark) : (loc.start < midMark); 4459 (loc.start > midMark) : (loc.start < midMark);
4429 iChangeFlags(d->flags, movingSelectMarkStart_DocumentWidgetFlag, isCloserToStart); 4460 iChangeFlags(d->flags, movingSelectMarkStart_DocumentWidgetFlag, isCloserToStart);
@@ -4453,7 +4484,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
4453 if (d->flags & (selectWords_DocumentWidgetFlag | selectLines_DocumentWidgetFlag)) { 4484 if (d->flags & (selectWords_DocumentWidgetFlag | selectLines_DocumentWidgetFlag)) {
4454 extendRange_Rangecc( 4485 extendRange_Rangecc(
4455 &d->selectMark, 4486 &d->selectMark,
4456 range_String(source_GmDocument(d->doc)), 4487 range_String(source_GmDocument(view->doc)),
4457 (d->flags & movingSelectMarkStart_DocumentWidgetFlag ? moveStart_RangeExtension 4488 (d->flags & movingSelectMarkStart_DocumentWidgetFlag ? moveStart_RangeExtension
4458 : moveEnd_RangeExtension) | 4489 : moveEnd_RangeExtension) |
4459 (d->flags & selectWords_DocumentWidgetFlag ? word_RangeExtension 4490 (d->flags & selectWords_DocumentWidgetFlag ? word_RangeExtension
@@ -4491,7 +4522,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
4491 setFocus_Widget(NULL); 4522 setFocus_Widget(NULL);
4492 /* Tap in tap selection mode. */ 4523 /* Tap in tap selection mode. */
4493 if (flags_Widget(w) & touchDrag_WidgetFlag) { 4524 if (flags_Widget(w) & touchDrag_WidgetFlag) {
4494 const iRangecc tapLoc = sourceLoc_DocumentWidget_(d, pos_Click(&d->click)); 4525 const iRangecc tapLoc = sourceLoc_DocumentView_(view, pos_Click(&d->click));
4495 /* Tapping on the selection will show a menu. */ 4526 /* Tapping on the selection will show a menu. */
4496 const iRangecc mark = selectMark_DocumentWidget_(d); 4527 const iRangecc mark = selectMark_DocumentWidget_(d);
4497 if (tapLoc.start >= mark.start && tapLoc.end <= mark.end) { 4528 if (tapLoc.start >= mark.start && tapLoc.end <= mark.end) {
@@ -4515,18 +4546,18 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
4515 return iTrue; 4546 return iTrue;
4516 } 4547 }
4517 } 4548 }
4518 if (d->hoverPre) { 4549 if (view->hoverPre) {
4519 togglePreFold_DocumentWidget_(d, preId_GmRun(d->hoverPre)); 4550 togglePreFold_DocumentWidget_(d, preId_GmRun(view->hoverPre));
4520 return iTrue; 4551 return iTrue;
4521 } 4552 }
4522 if (d->hoverLink) { 4553 if (view->hoverLink) {
4523 /* TODO: Move this to a method. */ 4554 /* TODO: Move this to a method. */
4524 const iGmLinkId linkId = d->hoverLink->linkId; 4555 const iGmLinkId linkId = view->hoverLink->linkId;
4525 const iMediaId linkMedia = mediaId_GmRun(d->hoverLink); 4556 const iMediaId linkMedia = mediaId_GmRun(view->hoverLink);
4526 const int linkFlags = linkFlags_GmDocument(d->doc, linkId); 4557 const int linkFlags = linkFlags_GmDocument(view->doc, linkId);
4527 iAssert(linkId); 4558 iAssert(linkId);
4528 /* Media links are opened inline by default. */ 4559 /* Media links are opened inline by default. */
4529 if (isMediaLink_GmDocument(d->doc, linkId)) { 4560 if (isMediaLink_GmDocument(view->doc, linkId)) {
4530 if (linkFlags & content_GmLinkFlag && linkFlags & permanent_GmLinkFlag) { 4561 if (linkFlags & content_GmLinkFlag && linkFlags & permanent_GmLinkFlag) {
4531 /* We have the content and it cannot be dismissed, so nothing 4562 /* We have the content and it cannot be dismissed, so nothing
4532 further to do. */ 4563 further to do. */
@@ -4535,7 +4566,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
4535 if (!requestMedia_DocumentWidget_(d, linkId, iTrue)) { 4566 if (!requestMedia_DocumentWidget_(d, linkId, iTrue)) {
4536 if (linkFlags & content_GmLinkFlag) { 4567 if (linkFlags & content_GmLinkFlag) {
4537 /* Dismiss shown content on click. */ 4568 /* Dismiss shown content on click. */
4538 setData_Media(media_GmDocument(d->doc), 4569 setData_Media(media_GmDocument(view->doc),
4539 linkId, 4570 linkId,
4540 NULL, 4571 NULL,
4541 NULL, 4572 NULL,
@@ -4549,10 +4580,10 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
4549 be redone. */ 4580 be redone. */
4550 } 4581 }
4551 } 4582 }
4552 redoLayout_GmDocument(d->doc); 4583 redoLayout_GmDocument(view->doc);
4553 d->hoverLink = NULL; 4584 view->hoverLink = NULL;
4554 clampScroll_DocumentWidget_(d); 4585 clampScroll_DocumentView_(view);
4555 updateVisible_DocumentWidget_(d); 4586 updateVisible_DocumentView_(view);
4556 invalidate_DocumentWidget_(d); 4587 invalidate_DocumentWidget_(d);
4557 refresh_Widget(w); 4588 refresh_Widget(w);
4558 return iTrue; 4589 return iTrue;
@@ -4561,13 +4592,13 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
4561 /* Show the existing content again if we have it. */ 4592 /* Show the existing content again if we have it. */
4562 iMediaRequest *req = findMediaRequest_DocumentWidget_(d, linkId); 4593 iMediaRequest *req = findMediaRequest_DocumentWidget_(d, linkId);
4563 if (req) { 4594 if (req) {
4564 setData_Media(media_GmDocument(d->doc), 4595 setData_Media(media_GmDocument(view->doc),
4565 linkId, 4596 linkId,
4566 meta_GmRequest(req->req), 4597 meta_GmRequest(req->req),
4567 body_GmRequest(req->req), 4598 body_GmRequest(req->req),
4568 allowHide_MediaFlag); 4599 allowHide_MediaFlag);
4569 redoLayout_GmDocument(d->doc); 4600 redoLayout_GmDocument(view->doc);
4570 updateVisible_DocumentWidget_(d); 4601 updateVisible_DocumentView_(view);
4571 invalidate_DocumentWidget_(d); 4602 invalidate_DocumentWidget_(d);
4572 refresh_Widget(w); 4603 refresh_Widget(w);
4573 return iTrue; 4604 return iTrue;
@@ -4591,11 +4622,11 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
4591 postCommandf_Root(w->root, "open newtab:%d url:%s", 4622 postCommandf_Root(w->root, "open newtab:%d url:%s",
4592 tabMode, 4623 tabMode,
4593 cstr_String(absoluteUrl_String( 4624 cstr_String(absoluteUrl_String(
4594 d->mod.url, linkUrl_GmDocument(d->doc, linkId)))); 4625 d->mod.url, linkUrl_GmDocument(view->doc, linkId))));
4595 } 4626 }
4596 else { 4627 else {
4597 const iString *url = absoluteUrl_String( 4628 const iString *url = absoluteUrl_String(
4598 d->mod.url, linkUrl_GmDocument(d->doc, linkId)); 4629 d->mod.url, linkUrl_GmDocument(view->doc, linkId));
4599 makeQuestion_Widget( 4630 makeQuestion_Widget(
4600 uiTextCaution_ColorEscape "${heading.openlink}", 4631 uiTextCaution_ColorEscape "${heading.openlink}",
4601 format_CStr( 4632 format_CStr(
@@ -4633,7 +4664,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
4633iDeclareType(DrawContext) 4664iDeclareType(DrawContext)
4634 4665
4635struct Impl_DrawContext { 4666struct Impl_DrawContext {
4636 const iDocumentWidget *widget; 4667 const iDocumentView *view;
4637 iRect widgetBounds; 4668 iRect widgetBounds;
4638 iRect docBounds; 4669 iRect docBounds;
4639 iRangei vis; 4670 iRangei vis;
@@ -4677,7 +4708,7 @@ static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iCol
4677 } 4708 }
4678 if (~run->flags & decoration_GmRunFlag) { 4709 if (~run->flags & decoration_GmRunFlag) {
4679 const iInt2 visPos = 4710 const iInt2 visPos =
4680 add_I2(run->bounds.pos, addY_I2(d->viewPos, viewPos_DocumentWidget_(d->widget))); 4711 add_I2(run->bounds.pos, addY_I2(d->viewPos, viewPos_DocumentView_(d->view)));
4681 const iRect rangeRect = { addX_I2(visPos, x), init_I2(w, height_Rect(run->bounds)) }; 4712 const iRect rangeRect = { addX_I2(visPos, x), init_I2(w, height_Rect(run->bounds)) };
4682 if (rangeRect.size.x) { 4713 if (rangeRect.size.x) {
4683 fillRect_Paint(&d->paint, rangeRect, color); 4714 fillRect_Paint(&d->paint, rangeRect, color);
@@ -4692,12 +4723,12 @@ static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iCol
4692 /* Link URLs are not part of the visible document, so they are ignored above. Handle 4723 /* Link URLs are not part of the visible document, so they are ignored above. Handle
4693 these ranges as a special case. */ 4724 these ranges as a special case. */
4694 if (run->linkId && run->flags & decoration_GmRunFlag) { 4725 if (run->linkId && run->flags & decoration_GmRunFlag) {
4695 const iRangecc url = linkUrlRange_GmDocument(d->widget->doc, run->linkId); 4726 const iRangecc url = linkUrlRange_GmDocument(d->view->doc, run->linkId);
4696 if (contains_Range(&url, mark.start) && 4727 if (contains_Range(&url, mark.start) &&
4697 (contains_Range(&url, mark.end) || url.end == mark.end)) { 4728 (contains_Range(&url, mark.end) || url.end == mark.end)) {
4698 fillRect_Paint( 4729 fillRect_Paint(
4699 &d->paint, 4730 &d->paint,
4700 moved_Rect(run->visBounds, addY_I2(d->viewPos, viewPos_DocumentWidget_(d->widget))), 4731 moved_Rect(run->visBounds, addY_I2(d->viewPos, viewPos_DocumentView_(d->view))),
4701 color); 4732 color);
4702 } 4733 }
4703 } 4734 }
@@ -4706,8 +4737,8 @@ static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iCol
4706static void drawMark_DrawContext_(void *context, const iGmRun *run) { 4737static void drawMark_DrawContext_(void *context, const iGmRun *run) {
4707 iDrawContext *d = context; 4738 iDrawContext *d = context;
4708 if (!isMedia_GmRun(run)) { 4739 if (!isMedia_GmRun(run)) {
4709 fillRange_DrawContext_(d, run, uiMatching_ColorId, d->widget->foundMark, &d->inFoundMark); 4740 fillRange_DrawContext_(d, run, uiMatching_ColorId, d->view->owner->foundMark, &d->inFoundMark);
4710 fillRange_DrawContext_(d, run, uiMarked_ColorId, d->widget->selectMark, &d->inSelectMark); 4741 fillRange_DrawContext_(d, run, uiMarked_ColorId, d->view->owner->selectMark, &d->inSelectMark);
4711 } 4742 }
4712} 4743}
4713 4744
@@ -4723,7 +4754,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4723 } 4754 }
4724 } 4755 }
4725 if (run->mediaType == image_MediaType) { 4756 if (run->mediaType == image_MediaType) {
4726 SDL_Texture *tex = imageTexture_Media(media_GmDocument(d->widget->doc), mediaId_GmRun(run)); 4757 SDL_Texture *tex = imageTexture_Media(media_GmDocument(d->view->doc), mediaId_GmRun(run));
4727 const iRect dst = moved_Rect(run->visBounds, origin); 4758 const iRect dst = moved_Rect(run->visBounds, origin);
4728 if (tex) { 4759 if (tex) {
4729 fillRect_Paint(&d->paint, dst, tmBackground_ColorId); /* in case the image has alpha */ 4760 fillRect_Paint(&d->paint, dst, tmBackground_ColorId); /* in case the image has alpha */
@@ -4745,16 +4776,16 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4745 return; 4776 return;
4746 } 4777 }
4747 enum iColorId fg = run->color; 4778 enum iColorId fg = run->color;
4748 const iGmDocument *doc = d->widget->doc; 4779 const iGmDocument *doc = d->view->doc;
4749 const int linkFlags = linkFlags_GmDocument(doc, run->linkId); 4780 const int linkFlags = linkFlags_GmDocument(doc, run->linkId);
4750 /* Hover state of a link. */ 4781 /* Hover state of a link. */
4751 iBool isHover = 4782 iBool isHover =
4752 (run->linkId && d->widget->hoverLink && run->linkId == d->widget->hoverLink->linkId && 4783 (run->linkId && d->view->hoverLink && run->linkId == d->view->hoverLink->linkId &&
4753 ~run->flags & decoration_GmRunFlag); 4784 ~run->flags & decoration_GmRunFlag);
4754 /* Visible (scrolled) position of the run. */ 4785 /* Visible (scrolled) position of the run. */
4755 const iInt2 visPos = addX_I2(add_I2(run->visBounds.pos, origin), 4786 const iInt2 visPos = addX_I2(add_I2(run->visBounds.pos, origin),
4756 /* Preformatted runs can be scrolled. */ 4787 /* Preformatted runs can be scrolled. */
4757 runOffset_DocumentWidget_(d->widget, run)); 4788 runOffset_DocumentView_(d->view, run));
4758 const iRect visRect = { visPos, run->visBounds.size }; 4789 const iRect visRect = { visPos, run->visBounds.size };
4759 /* Fill the background. */ { 4790 /* Fill the background. */ {
4760#if 0 4791#if 0
@@ -4824,10 +4855,10 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4824 } 4855 }
4825 else { 4856 else {
4826 if (d->showLinkNumbers && run->linkId && run->flags & decoration_GmRunFlag) { 4857 if (d->showLinkNumbers && run->linkId && run->flags & decoration_GmRunFlag) {
4827 const size_t ord = visibleLinkOrdinal_DocumentWidget_(d->widget, run->linkId); 4858 const size_t ord = visibleLinkOrdinal_DocumentView_(d->view, run->linkId);
4828 if (ord >= d->widget->ordinalBase) { 4859 if (ord >= d->view->owner->ordinalBase) {
4829 const iChar ordChar = 4860 const iChar ordChar =
4830 linkOrdinalChar_DocumentWidget_(d->widget, ord - d->widget->ordinalBase); 4861 linkOrdinalChar_DocumentWidget_(d->view->owner, ord - d->view->owner->ordinalBase);
4831 if (ordChar) { 4862 if (ordChar) {
4832 const char *circle = "\u25ef"; /* Large Circle */ 4863 const char *circle = "\u25ef"; /* Large Circle */
4833 const int circleFont = FONT_ID(default_FontId, regular_FontStyle, contentRegular_FontSize); 4864 const int circleFont = FONT_ID(default_FontId, regular_FontStyle, contentRegular_FontSize);
@@ -4901,8 +4932,8 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4901 break; 4932 break;
4902 } 4933 }
4903 if (linkMedia.type != download_MediaType && /* can't cancel downloads currently */ 4934 if (linkMedia.type != download_MediaType && /* can't cancel downloads currently */
4904 linkMedia.type != image_MediaType && 4935 linkMedia.type != image_MediaType &&
4905 findMediaRequest_DocumentWidget_(d->widget, run->linkId)) { 4936 findMediaRequest_DocumentWidget_(d->view->owner, run->linkId)) {
4906 appendFormat_String( 4937 appendFormat_String(
4907 &text, " %s" close_Icon, isHover ? escape_Color(tmLinkText_ColorId) : ""); 4938 &text, " %s" close_Icon, isHover ? escape_Color(tmLinkText_ColorId) : "");
4908 } 4939 }
@@ -4922,7 +4953,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4922 deinit_String(&text); 4953 deinit_String(&text);
4923 } 4954 }
4924 else if (run->flags & endOfLine_GmRunFlag && 4955 else if (run->flags & endOfLine_GmRunFlag &&
4925 (mr = findMediaRequest_DocumentWidget_(d->widget, run->linkId)) != NULL) { 4956 (mr = findMediaRequest_DocumentWidget_(d->view->owner, run->linkId)) != NULL) {
4926 if (!isFinished_GmRequest(mr->req)) { 4957 if (!isFinished_GmRequest(mr->req)) {
4927 draw_Text(metaFont, 4958 draw_Text(metaFont,
4928 topRight_Rect(linkRect), 4959 topRight_Rect(linkRect),
@@ -4931,87 +4962,6 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4931 (float) bodySize_GmRequest(mr->req) / 1.0e6f); 4962 (float) bodySize_GmRequest(mr->req) / 1.0e6f);
4932 } 4963 }
4933 } 4964 }
4934#if 0
4935 else if (isHover) {
4936 /* TODO: Make this a dynamic overlay, not part of the VisBuf content. */
4937 const iGmLinkId linkId = d->widget->hoverLink->linkId;
4938 const iString * url = linkUrl_GmDocument(doc, linkId);
4939 const int flags = linkFlags;
4940 iUrl parts;
4941 init_Url(&parts, url);
4942 fg = linkColor_GmDocument(doc, linkId, textHover_GmLinkPart);
4943 const enum iGmLinkScheme scheme = scheme_GmLinkFlag(flags);
4944 const iBool showHost = (flags & humanReadable_GmLinkFlag &&
4945 (!isEmpty_Range(&parts.host) ||
4946 scheme == mailto_GmLinkScheme));
4947 const iBool showImage = (flags & imageFileExtension_GmLinkFlag) != 0;
4948 const iBool showAudio = (flags & audioFileExtension_GmLinkFlag) != 0;
4949 iString str;
4950 init_String(&str);
4951 /* Show scheme and host. */
4952 if (run->flags & endOfLine_GmRunFlag &&
4953 (flags & (imageFileExtension_GmLinkFlag | audioFileExtension_GmLinkFlag) ||
4954 showHost)) {
4955 format_String(
4956 &str,
4957 "%s%s%s%s%s",
4958 showHost ? "" : "",
4959 showHost
4960 ? (scheme == mailto_GmLinkScheme ? cstr_String(url)
4961 : scheme != gemini_GmLinkScheme ? format_CStr("%s://%s",
4962 cstr_Rangecc(parts.scheme),
4963 cstr_Rangecc(parts.host))
4964 : cstr_Rangecc(parts.host))
4965 : "",
4966 showHost && (showImage || showAudio) ? " \u2014" : "",
4967 showImage || showAudio
4968 ? escape_Color(fg)
4969 : escape_Color(linkColor_GmDocument(doc, run->linkId, domain_GmLinkPart)),
4970 showImage || showAudio
4971 ? format_CStr(showImage ? " %s " photo_Icon : " %s \U0001f3b5",
4972 cstr_Lang(showImage ? "link.hint.image" : "link.hint.audio"))
4973 : "");
4974 }
4975 if (run->flags & endOfLine_GmRunFlag && flags & visited_GmLinkFlag) {
4976 iDate date;
4977 init_Date(&date, linkTime_GmDocument(doc, run->linkId));
4978 appendCStr_String(&str, " \u2014 ");
4979 appendCStr_String(
4980 &str, escape_Color(linkColor_GmDocument(doc, run->linkId, visited_GmLinkPart)));
4981 iString *dateStr = format_Date(&date, "%b %d");
4982 append_String(&str, dateStr);
4983 delete_String(dateStr);
4984 }
4985 if (!isEmpty_String(&str)) {
4986 if (run->isRTL) {
4987 appendCStr_String(&str, " \u2014 ");
4988 }
4989 else {
4990 prependCStr_String(&str, " \u2014 ");
4991 }
4992 const iInt2 textSize = measure_Text(metaFont, cstr_String(&str)).bounds.size;
4993 int tx = topRight_Rect(linkRect).x;
4994 const char *msg = cstr_String(&str);
4995 if (run->isRTL) {
4996 tx = topLeft_Rect(linkRect).x - textSize.x;
4997 }
4998 if (tx + textSize.x > right_Rect(d->widgetBounds)) {
4999 tx = right_Rect(d->widgetBounds) - textSize.x;
5000 fillRect_Paint(&d->paint, (iRect){ init_I2(tx, top_Rect(linkRect)), textSize },
5001 uiBackground_ColorId);
5002 msg += 4; /* skip the space and dash */
5003 tx += measure_Text(metaFont, " \u2014").advance.x / 2;
5004 }
5005 drawAlign_Text(metaFont,
5006 init_I2(tx, top_Rect(linkRect)),
5007 linkColor_GmDocument(doc, run->linkId, domain_GmLinkPart),
5008 left_Alignment,
5009 "%s",
5010 msg);
5011 deinit_String(&str);
5012 }
5013 }
5014#endif
5015 } 4965 }
5016 if (0) { 4966 if (0) {
5017 drawRect_Paint(&d->paint, (iRect){ visPos, run->bounds.size }, green_ColorId); 4967 drawRect_Paint(&d->paint, (iRect){ visPos, run->bounds.size }, green_ColorId);
@@ -5030,16 +4980,16 @@ static int drawSideRect_(iPaint *p, iRect rect) {
5030 return fg; 4980 return fg;
5031} 4981}
5032 4982
5033static int sideElementAvailWidth_DocumentWidget_(const iDocumentWidget *d) { 4983static int sideElementAvailWidth_DocumentView_(const iDocumentView *d) {
5034 return left_Rect(documentBounds_DocumentWidget_(d)) - 4984 return left_Rect(documentBounds_DocumentView_(d)) -
5035 left_Rect(bounds_Widget(constAs_Widget(d))) - 2 * d->pageMargin * gap_UI; 4985 left_Rect(bounds_Widget(constAs_Widget(d->owner))) - 2 * d->pageMargin * gap_UI;
5036} 4986}
5037 4987
5038static iBool isSideHeadingVisible_DocumentWidget_(const iDocumentWidget *d) { 4988static iBool isSideHeadingVisible_DocumentView_(const iDocumentView *d) {
5039 return sideElementAvailWidth_DocumentWidget_(d) >= lineHeight_Text(banner_FontId) * 4.5f; 4989 return sideElementAvailWidth_DocumentView_(d) >= lineHeight_Text(banner_FontId) * 4.5f;
5040} 4990}
5041 4991
5042static void updateSideIconBuf_DocumentWidget_(const iDocumentWidget *d) { 4992static void updateSideIconBuf_DocumentView_(const iDocumentView *d) {
5043 if (!isExposed_Window(get_Window())) { 4993 if (!isExposed_Window(get_Window())) {
5044 return; 4994 return;
5045 } 4995 }
@@ -5050,20 +5000,20 @@ static void updateSideIconBuf_DocumentWidget_(const iDocumentWidget *d) {
5050 dbuf->sideIconBuf = NULL; 5000 dbuf->sideIconBuf = NULL;
5051 } 5001 }
5052// const iGmRun *banner = siteBanner_GmDocument(d->doc); 5002// const iGmRun *banner = siteBanner_GmDocument(d->doc);
5053 if (isEmpty_Banner(d->banner)) { 5003 if (isEmpty_Banner(d->owner->banner)) {
5054 return; 5004 return;
5055 } 5005 }
5056 const int margin = gap_UI * d->pageMargin; 5006 const int margin = gap_UI * d->pageMargin;
5057 const int minBannerSize = lineHeight_Text(banner_FontId) * 2; 5007 const int minBannerSize = lineHeight_Text(banner_FontId) * 2;
5058 const iChar icon = siteIcon_GmDocument(d->doc); 5008 const iChar icon = siteIcon_GmDocument(d->doc);
5059 const int avail = sideElementAvailWidth_DocumentWidget_(d) - margin; 5009 const int avail = sideElementAvailWidth_DocumentView_(d) - margin;
5060 iBool isHeadingVisible = isSideHeadingVisible_DocumentWidget_(d); 5010 iBool isHeadingVisible = isSideHeadingVisible_DocumentView_(d);
5061 /* Determine the required size. */ 5011 /* Determine the required size. */
5062 iInt2 bufSize = init1_I2(minBannerSize); 5012 iInt2 bufSize = init1_I2(minBannerSize);
5063 const int sideHeadingFont = FONT_ID(documentHeading_FontId, regular_FontStyle, contentBig_FontSize); 5013 const int sideHeadingFont = FONT_ID(documentHeading_FontId, regular_FontStyle, contentBig_FontSize);
5064 if (isHeadingVisible) { 5014 if (isHeadingVisible) {
5065 const iInt2 headingSize = measureWrapRange_Text(sideHeadingFont, avail, 5015 const iInt2 headingSize = measureWrapRange_Text(sideHeadingFont, avail,
5066 currentHeading_DocumentWidget_(d)).bounds.size; 5016 currentHeading_DocumentView_(d)).bounds.size;
5067 if (headingSize.x > 0) { 5017 if (headingSize.x > 0) {
5068 bufSize.y += gap_Text + headingSize.y; 5018 bufSize.y += gap_Text + headingSize.y;
5069 bufSize.x = iMax(bufSize.x, headingSize.x); 5019 bufSize.x = iMax(bufSize.x, headingSize.x);
@@ -5090,7 +5040,7 @@ static void updateSideIconBuf_DocumentWidget_(const iDocumentWidget *d) {
5090 drawCentered_Text(banner_FontId, iconRect, iTrue, fg, "%s", cstr_String(&str)); 5040 drawCentered_Text(banner_FontId, iconRect, iTrue, fg, "%s", cstr_String(&str));
5091 deinit_String(&str); 5041 deinit_String(&str);
5092 if (isHeadingVisible) { 5042 if (isHeadingVisible) {
5093 iRangecc text = currentHeading_DocumentWidget_(d); 5043 iRangecc text = currentHeading_DocumentView_(d);
5094 iInt2 pos = addY_I2(bottomLeft_Rect(iconRect), gap_Text); 5044 iInt2 pos = addY_I2(bottomLeft_Rect(iconRect), gap_Text);
5095 const int font = sideHeadingFont; 5045 const int font = sideHeadingFont;
5096 drawWrapRange_Text(font, pos, avail, tmBannerSideTitle_ColorId, text); 5046 drawWrapRange_Text(font, pos, avail, tmBannerSideTitle_ColorId, text);
@@ -5099,10 +5049,10 @@ static void updateSideIconBuf_DocumentWidget_(const iDocumentWidget *d) {
5099 SDL_SetTextureBlendMode(dbuf->sideIconBuf, SDL_BLENDMODE_BLEND); 5049 SDL_SetTextureBlendMode(dbuf->sideIconBuf, SDL_BLENDMODE_BLEND);
5100} 5050}
5101 5051
5102static void drawSideElements_DocumentWidget_(const iDocumentWidget *d) { 5052static void drawSideElements_DocumentView_(const iDocumentView *d) {
5103 const iWidget *w = constAs_Widget(d); 5053 const iWidget *w = constAs_Widget(d->owner);
5104 const iRect bounds = bounds_Widget(w); 5054 const iRect bounds = bounds_Widget(w);
5105 const iRect docBounds = documentBounds_DocumentWidget_(d); 5055 const iRect docBounds = documentBounds_DocumentView_(d);
5106 const int margin = gap_UI * d->pageMargin; 5056 const int margin = gap_UI * d->pageMargin;
5107 float opacity = value_Anim(&d->sideOpacity); 5057 float opacity = value_Anim(&d->sideOpacity);
5108 const int avail = left_Rect(docBounds) - left_Rect(bounds) - 2 * margin; 5058 const int avail = left_Rect(docBounds) - left_Rect(bounds) - 2 * margin;
@@ -5140,20 +5090,20 @@ static void drawSideElements_DocumentWidget_(const iDocumentWidget *d) {
5140 unsetClip_Paint(&p); 5090 unsetClip_Paint(&p);
5141} 5091}
5142 5092
5143static void drawMedia_DocumentWidget_(const iDocumentWidget *d, iPaint *p) { 5093static void drawMedia_DocumentView_(const iDocumentView *d, iPaint *p) {
5144 iConstForEach(PtrArray, i, &d->visibleMedia) { 5094 iConstForEach(PtrArray, i, &d->visibleMedia) {
5145 const iGmRun * run = i.ptr; 5095 const iGmRun * run = i.ptr;
5146 if (run->mediaType == audio_MediaType) { 5096 if (run->mediaType == audio_MediaType) {
5147 iPlayerUI ui; 5097 iPlayerUI ui;
5148 init_PlayerUI(&ui, 5098 init_PlayerUI(&ui,
5149 audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run)), 5099 audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run)),
5150 runRect_DocumentWidget_(d, run)); 5100 runRect_DocumentView_(d, run));
5151 draw_PlayerUI(&ui, p); 5101 draw_PlayerUI(&ui, p);
5152 } 5102 }
5153 else if (run->mediaType == download_MediaType) { 5103 else if (run->mediaType == download_MediaType) {
5154 iDownloadUI ui; 5104 iDownloadUI ui;
5155 init_DownloadUI(&ui, constMedia_GmDocument(d->doc), run->mediaId, 5105 init_DownloadUI(&ui, constMedia_GmDocument(d->doc), run->mediaId,
5156 runRect_DocumentWidget_(d, run)); 5106 runRect_DocumentView_(d, run));
5157 draw_DownloadUI(&ui, p); 5107 draw_DownloadUI(&ui, p);
5158 } 5108 }
5159 } 5109 }
@@ -5166,20 +5116,23 @@ static void extend_GmRunRange_(iGmRunRange *runs) {
5166 } 5116 }
5167} 5117}
5168 5118
5169static iBool render_DocumentWidget_(const iDocumentWidget *d, iDrawContext *ctx, iBool prerenderExtra) { 5119static iBool render_DocumentView_(const iDocumentView *d, iDrawContext *ctx, iBool prerenderExtra) {
5170 iBool didDraw = iFalse; 5120 iBool didDraw = iFalse;
5171 const iRect bounds = bounds_Widget(constAs_Widget(d)); 5121 const iRect bounds = bounds_Widget(constAs_Widget(d->owner));
5172 const iRect ctxWidgetBounds = init_Rect( 5122 const iRect ctxWidgetBounds =
5173 0, 0, width_Rect(bounds) - constAs_Widget(d->scroll)->rect.size.x, height_Rect(bounds)); 5123 init_Rect(0,
5174 const iRangei full = { 0, size_GmDocument(d->doc).y }; 5124 0,
5175 const iRangei vis = ctx->vis; 5125 width_Rect(bounds) - constAs_Widget(d->owner->scroll)->rect.size.x,
5176 iVisBuf *visBuf = d->visBuf; /* will be updated now */ 5126 height_Rect(bounds));
5127 const iRangei full = { 0, size_GmDocument(d->doc).y };
5128 const iRangei vis = ctx->vis;
5129 iVisBuf *visBuf = d->visBuf; /* will be updated now */
5177 d->drawBufs->lastRenderTime = SDL_GetTicks(); 5130 d->drawBufs->lastRenderTime = SDL_GetTicks();
5178 /* Swap buffers around to have room available both before and after the visible region. */ 5131 /* Swap buffers around to have room available both before and after the visible region. */
5179 allocVisBuffer_DocumentWidget_(d); 5132 allocVisBuffer_DocumentView_(d);
5180 reposition_VisBuf(visBuf, vis); 5133 reposition_VisBuf(visBuf, vis);
5181 /* Redraw the invalid ranges. */ 5134 /* Redraw the invalid ranges. */
5182 if (~flags_Widget(constAs_Widget(d)) & destroyPending_WidgetFlag) { 5135 if (~flags_Widget(constAs_Widget(d->owner)) & destroyPending_WidgetFlag) {
5183 iPaint *p = &ctx->paint; 5136 iPaint *p = &ctx->paint;
5184 init_Paint(p); 5137 init_Paint(p);
5185 iForIndices(i, visBuf->buffers) { 5138 iForIndices(i, visBuf->buffers) {
@@ -5326,6 +5279,7 @@ static iBool render_DocumentWidget_(const iDocumentWidget *d, iDrawContext *ctx,
5326} 5279}
5327 5280
5328static void prerender_DocumentWidget_(iAny *context) { 5281static void prerender_DocumentWidget_(iAny *context) {
5282 iAssert(isInstance_Object(context, &Class_DocumentWidget));
5329 if (current_Root() == NULL) { 5283 if (current_Root() == NULL) {
5330 /* The widget has probably been removed from the widget tree, pending destruction. 5284 /* The widget has probably been removed from the widget tree, pending destruction.
5331 Tickers are not cancelled until the widget is actually destroyed. */ 5285 Tickers are not cancelled until the widget is actually destroyed. */
@@ -5333,16 +5287,16 @@ static void prerender_DocumentWidget_(iAny *context) {
5333 } 5287 }
5334 const iDocumentWidget *d = context; 5288 const iDocumentWidget *d = context;
5335 iDrawContext ctx = { 5289 iDrawContext ctx = {
5336 .widget = d, 5290 .view = &d->view,
5337 .docBounds = documentBounds_DocumentWidget_(d), 5291 .docBounds = documentBounds_DocumentView_(&d->view),
5338 .vis = visibleRange_DocumentWidget_(d), 5292 .vis = visibleRange_DocumentView_(&d->view),
5339 .showLinkNumbers = (d->flags & showLinkNumbers_DocumentWidgetFlag) != 0 5293 .showLinkNumbers = (d->flags & showLinkNumbers_DocumentWidgetFlag) != 0
5340 }; 5294 };
5341// printf("%u prerendering\n", SDL_GetTicks()); 5295// printf("%u prerendering\n", SDL_GetTicks());
5342 if (d->visBuf->buffers[0].texture) { 5296 if (d->view.visBuf->buffers[0].texture) {
5343 makePaletteGlobal_GmDocument(d->doc); 5297 makePaletteGlobal_GmDocument(d->view.doc);
5344 if (render_DocumentWidget_(d, &ctx, iTrue /* just fill up progressively */)) { 5298 if (render_DocumentView_(&d->view, &ctx, iTrue /* just fill up progressively */)) {
5345 /* Something was drawn, should check if there is still more to do. */ 5299 /* Something was drawn, should check later if there is still more to do. */
5346 addTicker_App(prerender_DocumentWidget_, context); 5300 addTicker_App(prerender_DocumentWidget_, context);
5347 } 5301 }
5348 } 5302 }
@@ -5358,47 +5312,44 @@ static void checkPendingInvalidation_DocumentWidget_(const iDocumentWidget *d) {
5358 } 5312 }
5359} 5313}
5360 5314
5361static void draw_DocumentWidget_(const iDocumentWidget *d) { 5315static void draw_DocumentView_(const iDocumentView *d) {
5362 const iWidget *w = constAs_Widget(d); 5316 const iWidget *w = constAs_Widget(d->owner);
5363 const iRect bounds = bounds_Widget(w); 5317 const iRect bounds = bounds_Widget(w);
5364 const iRect boundsWithoutVisOff = boundsWithoutVisualOffset_Widget(w); 5318 const iRect boundsWithoutVisOff = boundsWithoutVisualOffset_Widget(w);
5365 const iRect clipBounds = intersect_Rect(bounds, boundsWithoutVisOff); 5319 const iRect clipBounds = intersect_Rect(bounds, boundsWithoutVisOff);
5366 if (width_Rect(bounds) <= 0) {
5367 return;
5368 }
5369 checkPendingInvalidation_DocumentWidget_(d);
5370 /* Each document has its own palette, but the drawing routines rely on a global one. 5320 /* Each document has its own palette, but the drawing routines rely on a global one.
5371 As we're now drawing a document, ensure that the right palette is in effect. 5321 As we're now drawing a document, ensure that the right palette is in effect.
5372 Document theme colors can be used elsewhere, too, but first a document's palette 5322 Document theme colors can be used elsewhere, too, but first a document's palette
5373 must be made global. */ 5323 must be made global. */
5374 makePaletteGlobal_GmDocument(d->doc); 5324 makePaletteGlobal_GmDocument(d->doc);
5375 if (d->drawBufs->flags & updateTimestampBuf_DrawBufsFlag) { 5325 if (d->drawBufs->flags & updateTimestampBuf_DrawBufsFlag) {
5376 updateTimestampBuf_DocumentWidget_(d); 5326 updateTimestampBuf_DocumentView_(d);
5377 } 5327 }
5378 if (d->drawBufs->flags & updateSideBuf_DrawBufsFlag) { 5328 if (d->drawBufs->flags & updateSideBuf_DrawBufsFlag) {
5379 updateSideIconBuf_DocumentWidget_(d); 5329 updateSideIconBuf_DocumentView_(d);
5380 } 5330 }
5381 const iRect docBounds = documentBounds_DocumentWidget_(d); 5331 const iRect docBounds = documentBounds_DocumentView_(d);
5382 const iRangei vis = visibleRange_DocumentWidget_(d); 5332 const iRangei vis = visibleRange_DocumentView_(d);
5383 iDrawContext ctx = { 5333 iDrawContext ctx = {
5384 .widget = d, 5334 .view = d,
5385 .docBounds = docBounds, 5335 .docBounds = docBounds,
5386 .vis = vis, 5336 .vis = vis,
5387 .showLinkNumbers = (d->flags & showLinkNumbers_DocumentWidgetFlag) != 0, 5337 .showLinkNumbers = (d->owner->flags & showLinkNumbers_DocumentWidgetFlag) != 0,
5388 }; 5338 };
5389 init_Paint(&ctx.paint); 5339 init_Paint(&ctx.paint);
5390 render_DocumentWidget_(d, &ctx, iFalse /* just the mandatory parts */); 5340 render_DocumentView_(d, &ctx, iFalse /* just the mandatory parts */);
5391 int yTop = docBounds.pos.y + viewPos_DocumentWidget_(d); 5341 iBanner *banner = d->owner->banner;
5342 int yTop = docBounds.pos.y + viewPos_DocumentView_(d);
5392 const iBool isDocEmpty = size_GmDocument(d->doc).y == 0; 5343 const iBool isDocEmpty = size_GmDocument(d->doc).y == 0;
5393 const iBool isTouchSelecting = (flags_Widget(w) & touchDrag_WidgetFlag) != 0; 5344 const iBool isTouchSelecting = (flags_Widget(w) & touchDrag_WidgetFlag) != 0;
5394 if (!isDocEmpty || !isEmpty_Banner(d->banner)) { 5345 if (!isDocEmpty || !isEmpty_Banner(banner)) {
5395 const int docBgColor = isDocEmpty ? tmBannerBackground_ColorId : tmBackground_ColorId; 5346 const int docBgColor = isDocEmpty ? tmBannerBackground_ColorId : tmBackground_ColorId;
5396 setClip_Paint(&ctx.paint, clipBounds); 5347 setClip_Paint(&ctx.paint, clipBounds);
5397 if (!isDocEmpty) { 5348 if (!isDocEmpty) {
5398 draw_VisBuf(d->visBuf, init_I2(bounds.pos.x, yTop), ySpan_Rect(bounds)); 5349 draw_VisBuf(d->visBuf, init_I2(bounds.pos.x, yTop), ySpan_Rect(bounds));
5399 } 5350 }
5400 /* Text markers. */ 5351 /* Text markers. */
5401 if (!isEmpty_Range(&d->foundMark) || !isEmpty_Range(&d->selectMark)) { 5352 if (!isEmpty_Range(&d->owner->foundMark) || !isEmpty_Range(&d->owner->selectMark)) {
5402 SDL_Renderer *render = renderer_Window(get_Window()); 5353 SDL_Renderer *render = renderer_Window(get_Window());
5403 ctx.firstMarkRect = zero_Rect(); 5354 ctx.firstMarkRect = zero_Rect();
5404 ctx.lastMarkRect = zero_Rect(); 5355 ctx.lastMarkRect = zero_Rect();
@@ -5408,14 +5359,14 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
5408 ctx.viewPos = topLeft_Rect(docBounds); 5359 ctx.viewPos = topLeft_Rect(docBounds);
5409 /* Marker starting outside the visible range? */ 5360 /* Marker starting outside the visible range? */
5410 if (d->visibleRuns.start) { 5361 if (d->visibleRuns.start) {
5411 if (!isEmpty_Range(&d->selectMark) && 5362 if (!isEmpty_Range(&d->owner->selectMark) &&
5412 d->selectMark.start < d->visibleRuns.start->text.start && 5363 d->owner->selectMark.start < d->visibleRuns.start->text.start &&
5413 d->selectMark.end > d->visibleRuns.start->text.start) { 5364 d->owner->selectMark.end > d->visibleRuns.start->text.start) {
5414 ctx.inSelectMark = iTrue; 5365 ctx.inSelectMark = iTrue;
5415 } 5366 }
5416 if (isEmpty_Range(&d->foundMark) && 5367 if (isEmpty_Range(&d->owner->foundMark) &&
5417 d->foundMark.start < d->visibleRuns.start->text.start && 5368 d->owner->foundMark.start < d->visibleRuns.start->text.start &&
5418 d->foundMark.end > d->visibleRuns.start->text.start) { 5369 d->owner->foundMark.end > d->visibleRuns.start->text.start) {
5419 ctx.inFoundMark = iTrue; 5370 ctx.inFoundMark = iTrue;
5420 } 5371 }
5421 } 5372 }
@@ -5427,26 +5378,26 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
5427 drawPin_Paint(&ctx.paint, ctx.lastMarkRect, 1, tmQuote_ColorId); 5378 drawPin_Paint(&ctx.paint, ctx.lastMarkRect, 1, tmQuote_ColorId);
5428 } 5379 }
5429 } 5380 }
5430 drawMedia_DocumentWidget_(d, &ctx.paint); 5381 drawMedia_DocumentView_(d, &ctx.paint);
5431 /* Fill the top and bottom, in case the document is short. */ 5382 /* Fill the top and bottom, in case the document is short. */
5432 if (yTop > top_Rect(bounds)) { 5383 if (yTop > top_Rect(bounds)) {
5433 fillRect_Paint(&ctx.paint, 5384 fillRect_Paint(&ctx.paint,
5434 (iRect){ bounds.pos, init_I2(bounds.size.x, yTop - top_Rect(bounds)) }, 5385 (iRect){ bounds.pos, init_I2(bounds.size.x, yTop - top_Rect(bounds)) },
5435 !isEmpty_Banner(d->banner) ? tmBannerBackground_ColorId 5386 !isEmpty_Banner(banner) ? tmBannerBackground_ColorId
5436 : docBgColor); 5387 : docBgColor);
5437 } 5388 }
5438 /* Banner. */ 5389 /* Banner. */
5439 if (!isDocEmpty || numItems_Banner(d->banner) > 0) { 5390 if (!isDocEmpty || numItems_Banner(banner) > 0) {
5440 /* Fill the part between the banner and the top of the document. */ 5391 /* Fill the part between the banner and the top of the document. */
5441 fillRect_Paint(&ctx.paint, 5392 fillRect_Paint(&ctx.paint,
5442 (iRect){ init_I2(left_Rect(bounds), 5393 (iRect){ init_I2(left_Rect(bounds),
5443 top_Rect(docBounds) + viewPos_DocumentWidget_(d) - 5394 top_Rect(docBounds) + viewPos_DocumentView_(d) -
5444 documentTopPad_DocumentWidget_(d)), 5395 documentTopPad_DocumentView_(d)),
5445 init_I2(bounds.size.x, documentTopPad_DocumentWidget_(d)) }, 5396 init_I2(bounds.size.x, documentTopPad_DocumentView_(d)) },
5446 docBgColor); 5397 docBgColor);
5447 setPos_Banner(d->banner, addY_I2(topLeft_Rect(docBounds), 5398 setPos_Banner(banner, addY_I2(topLeft_Rect(docBounds),
5448 -pos_SmoothScroll(&d->scrollY))); 5399 -pos_SmoothScroll(&d->scrollY)));
5449 draw_Banner(d->banner); 5400 draw_Banner(banner);
5450 } 5401 }
5451 const int yBottom = yTop + size_GmDocument(d->doc).y; 5402 const int yBottom = yTop + size_GmDocument(d->doc).y;
5452 if (yBottom < bottom_Rect(bounds)) { 5403 if (yBottom < bottom_Rect(bounds)) {
@@ -5455,60 +5406,107 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
5455 !isDocEmpty ? docBgColor : tmBannerBackground_ColorId); 5406 !isDocEmpty ? docBgColor : tmBannerBackground_ColorId);
5456 } 5407 }
5457 unsetClip_Paint(&ctx.paint); 5408 unsetClip_Paint(&ctx.paint);
5458 drawSideElements_DocumentWidget_(d); 5409 drawSideElements_DocumentView_(d);
5459 if (deviceType_App() == desktop_AppDeviceType && prefs_App()->hoverLink && d->linkInfo) { 5410 /* Alt text. */
5460 const int pad = gap_UI; 5411 const float altTextOpacity = value_Anim(&d->altTextOpacity) * 6 - 5;
5461 update_LinkInfo(d->linkInfo, 5412 if (d->hoverAltPre && altTextOpacity > 0) {
5462 d->doc, 5413 const iGmPreMeta *meta = preMeta_GmDocument(d->doc, preId_GmRun(d->hoverAltPre));
5463 d->hoverLink ? d->hoverLink->linkId : 0, 5414 if (meta->flags & topLeft_GmPreMetaFlag && ~meta->flags & decoration_GmRunFlag &&
5464 width_Rect(bounds) - 2 * pad); 5415 !isEmpty_Range(&meta->altText)) {
5465 const iInt2 infoSize = size_LinkInfo(d->linkInfo); 5416 const int margin = 3 * gap_UI / 2;
5466 iInt2 infoPos = add_I2(bottomLeft_Rect(bounds), init_I2(pad, -infoSize.y - pad)); 5417 const int altFont = uiLabel_FontId;
5467 if (d->hoverLink) { 5418 const int wrap = docBounds.size.x - 2 * margin;
5468 const iRect runRect = runRect_DocumentWidget_(d, d->hoverLink); 5419 iInt2 pos = addY_I2(add_I2(docBounds.pos, meta->pixelRect.pos),
5469 d->linkInfo->isAltPos = 5420 viewPos_DocumentView_(d));
5470 (bottom_Rect(runRect) >= infoPos.y - lineHeight_Text(paragraph_FontId)); 5421 const iInt2 textSize = measureWrapRange_Text(altFont, wrap, meta->altText).bounds.size;
5471 } 5422 pos.y -= textSize.y + gap_UI;
5472 if (d->linkInfo->isAltPos) { 5423 pos.y = iMax(pos.y, top_Rect(bounds));
5473 infoPos.y = top_Rect(bounds) + pad; 5424 const iRect altRect = { pos, init_I2(docBounds.size.x, textSize.y) };
5425 ctx.paint.alpha = altTextOpacity * 255;
5426 if (altTextOpacity < 1) {
5427 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND);
5428 }
5429 fillRect_Paint(&ctx.paint, altRect, tmBackgroundAltText_ColorId);
5430 drawRect_Paint(&ctx.paint, altRect, tmFrameAltText_ColorId);
5431 setOpacity_Text(altTextOpacity);
5432 drawWrapRange_Text(altFont, addX_I2(pos, margin), wrap,
5433 tmQuote_ColorId, meta->altText);
5434 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE);
5435 setOpacity_Text(1.0f);
5474 } 5436 }
5475 draw_LinkInfo(d->linkInfo, infoPos);
5476 } 5437 }
5477#if 0 5438 /* Touch selection indicator. */
5478 if (prefs_App()->hoverLink && d->hoverLink) { 5439 if (isTouchSelecting) {
5479 const int font = uiLabel_FontId; 5440 iRect rect = { topLeft_Rect(bounds),
5480 const iRangecc linkUrl = range_String(linkUrl_GmDocument(d->doc, d->hoverLink->linkId)); 5441 init_I2(width_Rect(bounds), lineHeight_Text(uiLabelBold_FontId)) };
5481 const iInt2 size = measureRange_Text(font, linkUrl).bounds.size; 5442 fillRect_Paint(&ctx.paint, rect, uiTextAction_ColorId);
5482 const iRect linkRect = { addY_I2(bottomLeft_Rect(bounds), -size.y), 5443 const iRangecc mark = selectMark_DocumentWidget_(d->owner);
5483 addX_I2(size, 2 * gap_UI) }; 5444 drawCentered_Text(uiLabelBold_FontId,
5484 fillRect_Paint(&ctx.paint, linkRect, tmBackground_ColorId); 5445 rect,
5485 drawRange_Text(font, addX_I2(topLeft_Rect(linkRect), gap_UI), tmParagraph_ColorId, linkUrl); 5446 iFalse,
5447 uiBackground_ColorId,
5448 "%zu bytes selected", /* TODO: i18n */
5449 size_Range(&mark));
5486 } 5450 }
5487#endif
5488 } 5451 }
5452}
5453
5454static void draw_DocumentWidget_(const iDocumentWidget *d) {
5455 const iWidget *w = constAs_Widget(d);
5456 const iRect bounds = bounds_Widget(w);
5457 const iRect boundsWithoutVisOff = boundsWithoutVisualOffset_Widget(w);
5458 const iRect clipBounds = intersect_Rect(bounds, boundsWithoutVisOff);
5459 if (width_Rect(bounds) <= 0) {
5460 return;
5461 }
5462 checkPendingInvalidation_DocumentWidget_(d);
5463 draw_DocumentView_(&d->view);
5464 iPaint p;
5465 init_Paint(&p);
5489 if (colorTheme_App() == pureWhite_ColorTheme) { 5466 if (colorTheme_App() == pureWhite_ColorTheme) {
5490 drawHLine_Paint(&ctx.paint, topLeft_Rect(bounds), width_Rect(bounds), uiSeparator_ColorId); 5467 drawHLine_Paint(&p, topLeft_Rect(bounds), width_Rect(bounds), uiSeparator_ColorId);
5491 } 5468 }
5492 /* Pull action indicator. */ 5469 /* Pull action indicator. */
5493 if (deviceType_App() != desktop_AppDeviceType) { 5470 if (deviceType_App() != desktop_AppDeviceType) {
5494 float pullPos = pullActionPos_SmoothScroll(&d->scrollY); 5471 float pullPos = pullActionPos_SmoothScroll(&d->view.scrollY);
5495 /* Account for the part where the indicator isn't yet visible. */ 5472 /* Account for the part where the indicator isn't yet visible. */
5496 pullPos = (pullPos - 0.2f) / 0.8f; 5473 pullPos = (pullPos - 0.2f) / 0.8f;
5497 iRect indRect = initCentered_Rect(init_I2(mid_Rect(bounds).x, 5474 iRect indRect = initCentered_Rect(init_I2(mid_Rect(bounds).x,
5498 top_Rect(bounds) - 5 * gap_UI - 5475 top_Rect(bounds) - 5 * gap_UI -
5499 pos_SmoothScroll(&d->scrollY)), 5476 pos_SmoothScroll(&d->view.scrollY)),
5500 init_I2(20 * gap_UI, 2 * gap_UI)); 5477 init_I2(20 * gap_UI, 2 * gap_UI));
5501 setClip_Paint(&ctx.paint, clipBounds); 5478 setClip_Paint(&p, clipBounds);
5502 int color = pullPos < 1.0f ? tmBannerItemFrame_ColorId : tmBannerItemText_ColorId; 5479 int color = pullPos < 1.0f ? tmBannerItemFrame_ColorId : tmBannerItemText_ColorId;
5503 drawRect_Paint(&ctx.paint, indRect, color); 5480 drawRect_Paint(&p, indRect, color);
5504 if (pullPos > 0) { 5481 if (pullPos > 0) {
5505 shrink_Rect(&indRect, divi_I2(gap2_UI, 2)); 5482 shrink_Rect(&indRect, divi_I2(gap2_UI, 2));
5506 indRect.size.x *= pullPos; 5483 indRect.size.x *= pullPos;
5507 fillRect_Paint(&ctx.paint, indRect, color); 5484 fillRect_Paint(&p, indRect, color);
5508 } 5485 }
5509 unsetClip_Paint(&ctx.paint); 5486 unsetClip_Paint(&p);
5510 } 5487 }
5488 /* Scroll bar. */
5511 drawChildren_Widget(w); 5489 drawChildren_Widget(w);
5490 /* Information about the hovered link. */
5491 if (deviceType_App() == desktop_AppDeviceType && prefs_App()->hoverLink && d->linkInfo) {
5492 const int pad = gap_UI;
5493 update_LinkInfo(d->linkInfo,
5494 d->view.doc,
5495 d->view.hoverLink ? d->view.hoverLink->linkId : 0,
5496 width_Rect(bounds) - 2 * pad);
5497 const iInt2 infoSize = size_LinkInfo(d->linkInfo);
5498 iInt2 infoPos = add_I2(bottomLeft_Rect(bounds), init_I2(pad, -infoSize.y - pad));
5499 if (d->view.hoverLink) {
5500 const iRect runRect = runRect_DocumentView_(&d->view, d->view.hoverLink);
5501 d->linkInfo->isAltPos =
5502 (bottom_Rect(runRect) >= infoPos.y - lineHeight_Text(paragraph_FontId));
5503 }
5504 if (d->linkInfo->isAltPos) {
5505 infoPos.y = top_Rect(bounds) + pad;
5506 }
5507 draw_LinkInfo(d->linkInfo, infoPos);
5508 }
5509 /* Full-sized download indicator. */
5512 if (d->flags & drawDownloadCounter_DocumentWidgetFlag && isRequestOngoing_DocumentWidget(d)) { 5510 if (d->flags & drawDownloadCounter_DocumentWidgetFlag && isRequestOngoing_DocumentWidget(d)) {
5513 const int font = uiLabelLarge_FontId; 5511 const int font = uiLabelLarge_FontId;
5514 const iInt2 sevenSegWidth = measureRange_Text(font, range_CStr("\U0001fbf0")).bounds.size; 5512 const iInt2 sevenSegWidth = measureRange_Text(font, range_CStr("\U0001fbf0")).bounds.size;
@@ -5518,62 +5516,26 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
5518 tmQuote_ColorId, tmQuoteIcon_ColorId, 5516 tmQuote_ColorId, tmQuoteIcon_ColorId,
5519 bodySize_GmRequest(d->request)); 5517 bodySize_GmRequest(d->request));
5520 } 5518 }
5521 /* Alt text. */
5522 const float altTextOpacity = value_Anim(&d->altTextOpacity) * 6 - 5;
5523 if (d->hoverAltPre && altTextOpacity > 0) {
5524 const iGmPreMeta *meta = preMeta_GmDocument(d->doc, preId_GmRun(d->hoverAltPre));
5525 if (meta->flags & topLeft_GmPreMetaFlag && ~meta->flags & decoration_GmRunFlag &&
5526 !isEmpty_Range(&meta->altText)) {
5527 const int margin = 3 * gap_UI / 2;
5528 const int altFont = uiLabel_FontId;
5529 const int wrap = docBounds.size.x - 2 * margin;
5530 iInt2 pos = addY_I2(add_I2(docBounds.pos, meta->pixelRect.pos),
5531 viewPos_DocumentWidget_(d));
5532 const iInt2 textSize = measureWrapRange_Text(altFont, wrap, meta->altText).bounds.size;
5533 pos.y -= textSize.y + gap_UI;
5534 pos.y = iMax(pos.y, top_Rect(bounds));
5535 const iRect altRect = { pos, init_I2(docBounds.size.x, textSize.y) };
5536 ctx.paint.alpha = altTextOpacity * 255;
5537 if (altTextOpacity < 1) {
5538 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND);
5539 }
5540 fillRect_Paint(&ctx.paint, altRect, tmBackgroundAltText_ColorId);
5541 drawRect_Paint(&ctx.paint, altRect, tmFrameAltText_ColorId);
5542 setOpacity_Text(altTextOpacity);
5543 drawWrapRange_Text(altFont, addX_I2(pos, margin), wrap,
5544 tmQuote_ColorId, meta->altText);
5545 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE);
5546 setOpacity_Text(1.0f);
5547 }
5548 }
5549 /* Pinch zoom indicator. */ 5519 /* Pinch zoom indicator. */
5550 if (d->flags & pinchZoom_DocumentWidgetFlag) { 5520 if (d->flags & pinchZoom_DocumentWidgetFlag) {
5551 const int font = uiLabelLargeBold_FontId; 5521 const int font = uiLabelLargeBold_FontId;
5552 const int height = lineHeight_Text(font) * 2; 5522 const int height = lineHeight_Text(font) * 2;
5553 const iInt2 size = init_I2(height * 2, height); 5523 const iInt2 size = init_I2(height * 2, height);
5554 const iRect rect = { sub_I2(mid_Rect(bounds), divi_I2(size, 2)), size }; 5524 const iRect rect = { sub_I2(mid_Rect(bounds), divi_I2(size, 2)), size };
5555 fillRect_Paint(&ctx.paint, rect, d->pinchZoomPosted == 100 ? uiTextCaution_ColorId : uiTextAction_ColorId); 5525 fillRect_Paint(&p, rect, d->pinchZoomPosted == 100 ? uiTextCaution_ColorId : uiTextAction_ColorId);
5556 drawCentered_Text(font, bounds, iFalse, uiBackground_ColorId, "%d %%", 5526 drawCentered_Text(font, bounds, iFalse, uiBackground_ColorId, "%d %%",
5557 d->pinchZoomPosted); 5527 d->pinchZoomPosted);
5558 } 5528 }
5559 /* Touch selection indicator. */ 5529 /* Dimming during swipe animation. */
5560 if (isTouchSelecting) {
5561 iRect rect = { topLeft_Rect(bounds),
5562 init_I2(width_Rect(bounds), lineHeight_Text(uiLabelBold_FontId)) };
5563 fillRect_Paint(&ctx.paint, rect, uiTextAction_ColorId);
5564 const iRangecc mark = selectMark_DocumentWidget_(d);
5565 drawCentered_Text(uiLabelBold_FontId, rect, iFalse, uiBackground_ColorId, "%zu bytes selected",
5566 size_Range(&mark));
5567 }
5568 if (w->offsetRef) { 5530 if (w->offsetRef) {
5569 const int offX = visualOffsetByReference_Widget(w); 5531 const int offX = visualOffsetByReference_Widget(w);
5570 if (offX) { 5532 if (offX) {
5571 setClip_Paint(&ctx.paint, clipBounds); 5533 setClip_Paint(&p, clipBounds);
5572 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND); 5534 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND);
5573 ctx.paint.alpha = iAbs(offX) / (float) get_Window()->size.x * 300; 5535 p.alpha = iAbs(offX) / (float) get_Window()->size.x * 300;
5574 fillRect_Paint(&ctx.paint, bounds, backgroundFadeColor_Widget()); 5536 fillRect_Paint(&p, bounds, backgroundFadeColor_Widget());
5575 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); 5537 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE);
5576 unsetClip_Paint(&ctx.paint); 5538 unsetClip_Paint(&p);
5577 } 5539 }
5578 else { 5540 else {
5579 /* TODO: Should have a better place to do this; drawing is supposed to be immutable. */ 5541 /* TODO: Should have a better place to do this; drawing is supposed to be immutable. */
@@ -5582,11 +5544,11 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
5582 mut->flags &= ~refChildrenOffset_WidgetFlag; 5544 mut->flags &= ~refChildrenOffset_WidgetFlag;
5583 } 5545 }
5584 } 5546 }
5585// drawRect_Paint(&ctx.paint, docBounds, red_ColorId); 5547// drawRect_Paint(&p, docBounds, red_ColorId);
5586 if (deviceType_App() == phone_AppDeviceType) { 5548 if (deviceType_App() == phone_AppDeviceType) {
5587 /* The phone toolbar uses the palette of the active tab, but there may be other 5549 /* The phone toolbar uses the palette of the active tab, but there may be other
5588 documents drawn before the toolbar, causing the colors to be incorrect. */ 5550 documents drawn before the toolbar, causing the colors to be incorrect. */
5589 makePaletteGlobal_GmDocument(document_App()->doc); 5551 makePaletteGlobal_GmDocument(document_App()->view.doc);
5590 } 5552 }
5591} 5553}
5592 5554
@@ -5601,7 +5563,7 @@ const iString *url_DocumentWidget(const iDocumentWidget *d) {
5601} 5563}
5602 5564
5603const iGmDocument *document_DocumentWidget(const iDocumentWidget *d) { 5565const iGmDocument *document_DocumentWidget(const iDocumentWidget *d) {
5604 return d->doc; 5566 return d->view.doc;
5605} 5567}
5606 5568
5607const iBlock *sourceContent_DocumentWidget(const iDocumentWidget *d) { 5569const iBlock *sourceContent_DocumentWidget(const iDocumentWidget *d) {
@@ -5609,20 +5571,20 @@ const iBlock *sourceContent_DocumentWidget(const iDocumentWidget *d) {
5609} 5571}
5610 5572
5611int documentWidth_DocumentWidget(const iDocumentWidget *d) { 5573int documentWidth_DocumentWidget(const iDocumentWidget *d) {
5612 return documentWidth_DocumentWidget_(d); 5574 return documentWidth_DocumentView_(&d->view);
5613} 5575}
5614 5576
5615const iString *feedTitle_DocumentWidget(const iDocumentWidget *d) { 5577const iString *feedTitle_DocumentWidget(const iDocumentWidget *d) {
5616 if (!isEmpty_String(title_GmDocument(d->doc))) { 5578 if (!isEmpty_String(title_GmDocument(d->view.doc))) {
5617 return title_GmDocument(d->doc); 5579 return title_GmDocument(d->view.doc);
5618 } 5580 }
5619 return bookmarkTitle_DocumentWidget(d); 5581 return bookmarkTitle_DocumentWidget(d);
5620} 5582}
5621 5583
5622const iString *bookmarkTitle_DocumentWidget(const iDocumentWidget *d) { 5584const iString *bookmarkTitle_DocumentWidget(const iDocumentWidget *d) {
5623 iStringArray *title = iClob(new_StringArray()); 5585 iStringArray *title = iClob(new_StringArray());
5624 if (!isEmpty_String(title_GmDocument(d->doc))) { 5586 if (!isEmpty_String(title_GmDocument(d->view.doc))) {
5625 pushBack_StringArray(title, title_GmDocument(d->doc)); 5587 pushBack_StringArray(title, title_GmDocument(d->view.doc));
5626 } 5588 }
5627 if (!isEmpty_String(d->titleUser)) { 5589 if (!isEmpty_String(d->titleUser)) {
5628 pushBack_StringArray(title, d->titleUser); 5590 pushBack_StringArray(title, d->titleUser);
@@ -5682,7 +5644,7 @@ void setUrlAndSource_DocumentWidget(iDocumentWidget *d, const iString *url, cons
5682iDocumentWidget *duplicate_DocumentWidget(const iDocumentWidget *orig) { 5644iDocumentWidget *duplicate_DocumentWidget(const iDocumentWidget *orig) {
5683 iDocumentWidget *d = new_DocumentWidget(); 5645 iDocumentWidget *d = new_DocumentWidget();
5684 delete_History(d->mod.history); 5646 delete_History(d->mod.history);
5685 d->initNormScrollY = normScrollPos_DocumentWidget_(d); 5647 d->initNormScrollY = normScrollPos_DocumentView_(&d->view);
5686 d->mod.history = copy_History(orig->mod.history); 5648 d->mod.history = copy_History(orig->mod.history);
5687 setUrlFlags_DocumentWidget(d, orig->mod.url, useCachedContentIfAvailable_DocumentWidgetSetUrlFlag); 5649 setUrlFlags_DocumentWidget(d, orig->mod.url, useCachedContentIfAvailable_DocumentWidgetSetUrlFlag);
5688 return d; 5650 return d;
@@ -5733,11 +5695,12 @@ void takeRequest_DocumentWidget(iDocumentWidget *d, iGmRequest *finishedRequest)
5733} 5695}
5734 5696
5735void updateSize_DocumentWidget(iDocumentWidget *d) { 5697void updateSize_DocumentWidget(iDocumentWidget *d) {
5736 updateDocumentWidthRetainingScrollPosition_DocumentWidget_(d, iFalse); 5698 iDocumentView *view = &d->view;
5737 resetWideRuns_DocumentWidget_(d); 5699 updateDocumentWidthRetainingScrollPosition_DocumentView_(view, iFalse);
5738 d->drawBufs->flags |= updateSideBuf_DrawBufsFlag; 5700 resetWideRuns_DocumentView_(view);
5739 updateVisible_DocumentWidget_(d); 5701 view->drawBufs->flags |= updateSideBuf_DrawBufsFlag;
5740 setWidth_Banner(d->banner, documentWidth_DocumentWidget(d)); 5702 updateVisible_DocumentView_(view);
5703 setWidth_Banner(d->banner, documentWidth_DocumentView_(view));
5741 invalidate_DocumentWidget_(d); 5704 invalidate_DocumentWidget_(d);
5742 arrange_Widget(d->footerButtons); 5705 arrange_Widget(d->footerButtons);
5743} 5706}