summaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/documentwidget.c164
1 files changed, 150 insertions, 14 deletions
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 165bf9cf..27a26d99 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -21,20 +21,22 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ 21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22 22
23#include "documentwidget.h" 23#include "documentwidget.h"
24#include "scrollwidget.h" 24
25#include "inputwidget.h"
26#include "labelwidget.h"
27#include "visbuf.h"
28#include "paint.h"
29#include "command.h"
30#include "keys.h"
31#include "util.h"
32#include "history.h"
33#include "app.h" 25#include "app.h"
26#include "audio/player.h"
27#include "command.h"
34#include "gmdocument.h" 28#include "gmdocument.h"
35#include "gmrequest.h" 29#include "gmrequest.h"
36#include "gmutil.h" 30#include "gmutil.h"
31#include "history.h"
32#include "inputwidget.h"
33#include "keys.h"
34#include "labelwidget.h"
37#include "media.h" 35#include "media.h"
36#include "paint.h"
37#include "scrollwidget.h"
38#include "util.h"
39#include "visbuf.h"
38 40
39#include <the_Foundation/file.h> 41#include <the_Foundation/file.h>
40#include <the_Foundation/fileinfo.h> 42#include <the_Foundation/fileinfo.h>
@@ -170,6 +172,7 @@ struct Impl_DocumentWidget {
170 iRangecc foundMark; 172 iRangecc foundMark;
171 int pageMargin; 173 int pageMargin;
172 iPtrArray visibleLinks; 174 iPtrArray visibleLinks;
175 iPtrArray visiblePlayers; /* currently playing audio */
173 const iGmRun * hoverLink; 176 const iGmRun * hoverLink;
174 const iGmRun * contextLink; 177 const iGmRun * contextLink;
175 iBool noHoverWhileScrolling; 178 iBool noHoverWhileScrolling;
@@ -233,6 +236,7 @@ void init_DocumentWidget(iDocumentWidget *d) {
233 init_String(&d->sourceMime); 236 init_String(&d->sourceMime);
234 init_Block(&d->sourceContent, 0); 237 init_Block(&d->sourceContent, 0);
235 init_PtrArray(&d->visibleLinks); 238 init_PtrArray(&d->visibleLinks);
239 init_PtrArray(&d->visiblePlayers);
236 init_Click(&d->click, d, SDL_BUTTON_LEFT); 240 init_Click(&d->click, d, SDL_BUTTON_LEFT);
237 addChild_Widget(w, iClob(d->scroll = new_ScrollWidget())); 241 addChild_Widget(w, iClob(d->scroll = new_ScrollWidget()));
238 d->menu = NULL; /* created when clicking */ 242 d->menu = NULL; /* created when clicking */
@@ -253,6 +257,7 @@ void deinit_DocumentWidget(iDocumentWidget *d) {
253 deinit_Block(&d->sourceContent); 257 deinit_Block(&d->sourceContent);
254 deinit_String(&d->sourceMime); 258 deinit_String(&d->sourceMime);
255 iRelease(d->doc); 259 iRelease(d->doc);
260 deinit_PtrArray(&d->visiblePlayers);
256 deinit_PtrArray(&d->visibleLinks); 261 deinit_PtrArray(&d->visibleLinks);
257 delete_String(d->certSubject); 262 delete_String(d->certSubject);
258 delete_String(d->titleUser); 263 delete_String(d->titleUser);
@@ -333,7 +338,7 @@ static iRangei visibleRange_DocumentWidget_(const iDocumentWidget *d) {
333 d->scrollY + height_Rect(bounds_Widget(constAs_Widget(d))) - margin }; 338 d->scrollY + height_Rect(bounds_Widget(constAs_Widget(d))) - margin };
334} 339}
335 340
336static void addVisibleLink_DocumentWidget_(void *context, const iGmRun *run) { 341static void addVisible_DocumentWidget_(void *context, const iGmRun *run) {
337 iDocumentWidget *d = context; 342 iDocumentWidget *d = context;
338 if (~run->flags & decoration_GmRunFlag && !run->imageId) { 343 if (~run->flags & decoration_GmRunFlag && !run->imageId) {
339 if (!d->firstVisibleRun) { 344 if (!d->firstVisibleRun) {
@@ -341,6 +346,9 @@ static void addVisibleLink_DocumentWidget_(void *context, const iGmRun *run) {
341 } 346 }
342 d->lastVisibleRun = run; 347 d->lastVisibleRun = run;
343 } 348 }
349 if (run->audioId) {
350 pushBack_PtrArray(&d->visiblePlayers, run);
351 }
344 if (run->linkId && linkFlags_GmDocument(d->doc, run->linkId) & supportedProtocol_GmLinkFlag) { 352 if (run->linkId && linkFlags_GmDocument(d->doc, run->linkId) & supportedProtocol_GmLinkFlag) {
345 pushBack_PtrArray(&d->visibleLinks, run); 353 pushBack_PtrArray(&d->visibleLinks, run);
346 } 354 }
@@ -434,6 +442,18 @@ static void updateOutlineOpacity_DocumentWidget_(iDocumentWidget *d) {
434 animate_DocumentWidget_(d); 442 animate_DocumentWidget_(d);
435} 443}
436 444
445static void animatePlayingAudio_DocumentWidget_(void *widget) {
446 iDocumentWidget *d = widget;
447 iConstForEach(PtrArray, i, &d->visiblePlayers) {
448 const iGmRun *run = i.ptr;
449 iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), run->audioId);
450 if (isStarted_Player(plr) && !isPaused_Player(plr)) {
451 refresh_Widget(d);
452 addTicker_App(animatePlayingAudio_DocumentWidget_, d);
453 }
454 }
455}
456
437static void updateVisible_DocumentWidget_(iDocumentWidget *d) { 457static void updateVisible_DocumentWidget_(iDocumentWidget *d) {
438 const iRangei visRange = visibleRange_DocumentWidget_(d); 458 const iRangei visRange = visibleRange_DocumentWidget_(d);
439 const iRect bounds = bounds_Widget(as_Widget(d)); 459 const iRect bounds = bounds_Widget(as_Widget(d));
@@ -443,10 +463,12 @@ static void updateVisible_DocumentWidget_(iDocumentWidget *d) {
443 d->scrollY, 463 d->scrollY,
444 docSize > 0 ? height_Rect(bounds) * size_Range(&visRange) / docSize : 0); 464 docSize > 0 ? height_Rect(bounds) * size_Range(&visRange) / docSize : 0);
445 clear_PtrArray(&d->visibleLinks); 465 clear_PtrArray(&d->visibleLinks);
466 clear_PtrArray(&d->visiblePlayers);
446 d->firstVisibleRun = NULL; 467 d->firstVisibleRun = NULL;
447 render_GmDocument(d->doc, visRange, addVisibleLink_DocumentWidget_, d); 468 render_GmDocument(d->doc, visRange, addVisible_DocumentWidget_, d);
448 updateHover_DocumentWidget_(d, mouseCoord_Window(get_Window())); 469 updateHover_DocumentWidget_(d, mouseCoord_Window(get_Window()));
449 updateSideOpacity_DocumentWidget_(d, iTrue); 470 updateSideOpacity_DocumentWidget_(d, iTrue);
471 animatePlayingAudio_DocumentWidget_(d);
450 /* Remember scroll positions of recently visited pages. */ { 472 /* Remember scroll positions of recently visited pages. */ {
451 iRecentUrl *recent = mostRecentUrl_History(d->mod.history); 473 iRecentUrl *recent = mostRecentUrl_History(d->mod.history);
452 if (recent && docSize && d->state == ready_RequestState) { 474 if (recent && docSize && d->state == ready_RequestState) {
@@ -1919,8 +1941,8 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
1919 return; 1941 return;
1920 } 1942 }
1921 else if (run->audioId) { 1943 else if (run->audioId) {
1922 /* Draw the audio player interface. */ 1944 /* Audio player UI is drawn afterwards as a dynamic overlay. */
1923 fillRect_Paint(&d->paint, moved_Rect(run->visBounds, origin), red_ColorId); 1945 //fillRect_Paint(&d->paint, moved_Rect(run->visBounds, origin), red_ColorId);
1924 return; 1946 return;
1925 } 1947 }
1926 enum iColorId fg = run->color; 1948 enum iColorId fg = run->color;
@@ -2239,6 +2261,119 @@ static void drawSideElements_DocumentWidget_(const iDocumentWidget *d) {
2239 unsetClip_Paint(&p); 2261 unsetClip_Paint(&p);
2240} 2262}
2241 2263
2264iDeclareType(PlayerUI)
2265
2266struct Impl_PlayerUI {
2267 const iPlayer *player;
2268 iRect bounds;
2269 iRect playPauseRect;
2270 iRect rewindRect;
2271 iRect scrubberRect;
2272 iRect menuRect;
2273};
2274
2275static void init_PlayerUI(iPlayerUI *d, const iPlayer *player, iRect bounds) {
2276 d->player = player;
2277 d->bounds = bounds;
2278 const int height = height_Rect(bounds);
2279 d->playPauseRect = (iRect){ addX_I2(topLeft_Rect(bounds), gap_UI / 2), init1_I2(height) };
2280 d->rewindRect = (iRect){ topRight_Rect(d->playPauseRect), init1_I2(height) };
2281 d->menuRect = (iRect){ addX_I2(topRight_Rect(bounds), -height - gap_UI / 2), init1_I2(height) };
2282 d->scrubberRect = initCorners_Rect(topRight_Rect(d->rewindRect), bottomLeft_Rect(d->menuRect));
2283}
2284
2285static void drawPlayerButton_(iPaint *p, iRect rect, const char *label) {
2286 const iInt2 mouse = mouseCoord_Window(get_Window());
2287 const iBool isHover = contains_Rect(rect, mouse);
2288 const iBool isPressed = isHover && (SDL_GetMouseState(NULL, NULL) & SDL_BUTTON_LEFT) != 0;
2289 const int frame = (isPressed ? uiTextCaution_ColorId : isHover ? uiHeading_ColorId : uiAnnotation_ColorId);
2290 iRect frameRect = shrunk_Rect(rect, init_I2(gap_UI / 2, gap_UI));
2291 drawRect_Paint(p, frameRect, frame);
2292 if (isPressed) {
2293 fillRect_Paint(
2294 p,
2295 adjusted_Rect(shrunk_Rect(frameRect, divi_I2(gap2_UI, 2)), zero_I2(), one_I2()),
2296 frame);
2297 }
2298 const int fg = isPressed ? uiBackground_ColorId : frame;
2299 drawCentered_Text(uiContent_FontId, frameRect, iTrue, fg, "%s", label);
2300}
2301
2302static int drawSevenSegmentTime_(iInt2 pos, int color, int align, int seconds) { /* returns width */
2303 const uint32_t sevenSegmentDigit = 0x1fbf0;
2304 const int hours = seconds / 3600;
2305 const int mins = (seconds / 60) % 60;
2306 const int secs = seconds % 60;
2307 const int font = uiLabel_FontId;
2308 iString num;
2309 init_String(&num);
2310 if (hours) {
2311 appendChar_String(&num, sevenSegmentDigit + (hours % 10));
2312 appendChar_String(&num, ':');
2313 }
2314 appendChar_String(&num, sevenSegmentDigit + (mins / 10) % 10);
2315 appendChar_String(&num, sevenSegmentDigit + (mins % 10));
2316 appendChar_String(&num, ':');
2317 appendChar_String(&num, sevenSegmentDigit + (secs / 10) % 10);
2318 appendChar_String(&num, sevenSegmentDigit + (secs % 10));
2319 iInt2 size = advanceRange_Text(font, range_String(&num));
2320 if (align == right_Alignment) {
2321 pos.x -= size.x;
2322 }
2323 drawRange_Text(font, pos, color, range_String(&num));
2324 deinit_String(&num);
2325 return size.x;
2326}
2327
2328static void draw_PlayerUI(iPlayerUI *d, iPaint *p) {
2329 const int playerBackground_ColorId = uiBackground_ColorId;
2330 const int playerFrame_ColorId = uiSeparator_ColorId;
2331 fillRect_Paint(p, d->bounds, playerBackground_ColorId);
2332 drawRect_Paint(p, d->bounds, playerFrame_ColorId);
2333 drawPlayerButton_(p, d->playPauseRect, isPaused_Player(d->player) ? "\U0001f782" : "\u23f8");
2334 drawPlayerButton_(p, d->rewindRect, "\u23ee");
2335 drawPlayerButton_(p, d->menuRect, "\U0001d362");
2336 const int hgt = lineHeight_Text(uiLabel_FontId);
2337 const int yMid = mid_Rect(d->scrubberRect).y;
2338 const float playTime = time_Player(d->player);
2339 const float totalTime = duration_Player(d->player);
2340 const int bright = uiHeading_ColorId;
2341 const int dim = uiAnnotation_ColorId;
2342 int leftWidth = drawSevenSegmentTime_(
2343 init_I2(left_Rect(d->scrubberRect) + 2 * gap_UI, yMid - hgt / 2),
2344 isPaused_Player(d->player) ? dim : bright,
2345 left_Alignment,
2346 iRound(playTime));
2347 int rightWidth = drawSevenSegmentTime_(
2348 init_I2(right_Rect(d->scrubberRect) - 2 * gap_UI, yMid - hgt / 2),
2349 dim,
2350 right_Alignment,
2351 iRound(totalTime));
2352 /* Scrubber. */
2353 const int s1 = left_Rect(d->scrubberRect) + leftWidth + 6 * gap_UI;
2354 const int s2 = right_Rect(d->scrubberRect) - rightWidth - 6 * gap_UI;
2355 const float normPos = totalTime > 0 ? playTime / totalTime : 0.0f;
2356 const int part = (s2 - s1) * normPos;
2357 drawHLine_Paint(p, init_I2(s1, yMid), part, bright);
2358 drawHLine_Paint(p, init_I2(s1 + part, yMid), (s2 - s1) - part, dim);
2359 draw_Text(uiLabel_FontId,
2360 init_I2(s1 * (1.0f - normPos) + s2 * normPos - hgt / 3, yMid - hgt / 2),
2361 bright,
2362 "\u23fa");
2363}
2364
2365static void drawAudioPlayers_DocumentWidget_(const iDocumentWidget *d, iPaint *p) {
2366 const iRect docBounds = documentBounds_DocumentWidget_(d);
2367 iConstForEach(PtrArray, i, &d->visiblePlayers) {
2368 const iGmRun * run = i.ptr;
2369 const iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), run->audioId);
2370 const iRect rect = moved_Rect(run->bounds, addY_I2(topLeft_Rect(docBounds), -d->scrollY));
2371 iPlayerUI ui;
2372 init_PlayerUI(&ui, plr, rect);
2373 draw_PlayerUI(&ui, p);
2374 }
2375}
2376
2242static void draw_DocumentWidget_(const iDocumentWidget *d) { 2377static void draw_DocumentWidget_(const iDocumentWidget *d) {
2243 const iWidget *w = constAs_Widget(d); 2378 const iWidget *w = constAs_Widget(d);
2244 const iRect bounds = bounds_Widget(w); 2379 const iRect bounds = bounds_Widget(w);
@@ -2297,7 +2432,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
2297 } 2432 }
2298 } 2433 }
2299 endTarget_Paint(&ctx.paint); 2434 endTarget_Paint(&ctx.paint);
2300 fflush(stdout); 2435// fflush(stdout);
2301 } 2436 }
2302 validate_VisBuf(visBuf); 2437 validate_VisBuf(visBuf);
2303 clear_PtrSet(d->invalidRuns); 2438 clear_PtrSet(d->invalidRuns);
@@ -2314,6 +2449,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
2314 render_GmDocument(d->doc, vis, drawMark_DrawContext_, &ctx); 2449 render_GmDocument(d->doc, vis, drawMark_DrawContext_, &ctx);
2315 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); 2450 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE);
2316 } 2451 }
2452 drawAudioPlayers_DocumentWidget_(d, &ctx.paint);
2317 unsetClip_Paint(&ctx.paint); 2453 unsetClip_Paint(&ctx.paint);
2318 /* Fill the top and bottom, in case the document is short. */ 2454 /* Fill the top and bottom, in case the document is short. */
2319 if (yTop > top_Rect(bounds)) { 2455 if (yTop > top_Rect(bounds)) {