diff options
-rw-r--r-- | src/audio/player.c | 29 | ||||
-rw-r--r-- | src/audio/player.h | 3 | ||||
-rw-r--r-- | src/gmdocument.c | 2 | ||||
-rw-r--r-- | src/media.c | 8 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 164 |
5 files changed, 188 insertions, 18 deletions
diff --git a/src/audio/player.c b/src/audio/player.c index 177613bb..2d6767ea 100644 --- a/src/audio/player.c +++ b/src/audio/player.c | |||
@@ -146,6 +146,7 @@ iDeclareType(ContentSpec) | |||
146 | struct Impl_ContentSpec { | 146 | struct Impl_ContentSpec { |
147 | SDL_AudioFormat inputFormat; | 147 | SDL_AudioFormat inputFormat; |
148 | SDL_AudioSpec output; | 148 | SDL_AudioSpec output; |
149 | uint64_t totalSamples; | ||
149 | iRanges wavData; | 150 | iRanges wavData; |
150 | }; | 151 | }; |
151 | 152 | ||
@@ -168,6 +169,8 @@ struct Impl_Decoder { | |||
168 | size_t inputPos; | 169 | size_t inputPos; |
169 | iSampleBuf output; | 170 | iSampleBuf output; |
170 | iMutex outputMutex; | 171 | iMutex outputMutex; |
172 | uint64_t currentSample; | ||
173 | uint64_t totalSamples; /* zero if unknown */ | ||
171 | iRanges wavData; | 174 | iRanges wavData; |
172 | }; | 175 | }; |
173 | 176 | ||
@@ -252,6 +255,7 @@ static enum iDecoderParseStatus parseWav_Decoder_(iDecoder *d, iRanges inputRang | |||
252 | } | 255 | } |
253 | } | 256 | } |
254 | iGuardMutex(&d->outputMutex, write_SampleBuf(&d->output, samples, n)); | 257 | iGuardMutex(&d->outputMutex, write_SampleBuf(&d->output, samples, n)); |
258 | d->currentSample += n; | ||
255 | free(samples); | 259 | free(samples); |
256 | return ok_DecoderParseStatus; | 260 | return ok_DecoderParseStatus; |
257 | } | 261 | } |
@@ -305,6 +309,8 @@ void init_Decoder(iDecoder *d, iInputBuf *input, const iContentSpec *spec) { | |||
305 | spec->output.format, | 309 | spec->output.format, |
306 | spec->output.channels, | 310 | spec->output.channels, |
307 | spec->output.samples * 2); | 311 | spec->output.samples * 2); |
312 | d->currentSample = 0; | ||
313 | d->totalSamples = spec->totalSamples; | ||
308 | init_Mutex(&d->outputMutex); | 314 | init_Mutex(&d->outputMutex); |
309 | d->thread = new_Thread(run_Decoder_); | 315 | d->thread = new_Thread(run_Decoder_); |
310 | setUserData_Thread(d->thread, d); | 316 | setUserData_Thread(d->thread, d); |
@@ -366,6 +372,7 @@ static iContentSpec contentSpec_Player_(const iPlayer *d) { | |||
366 | return content; | 372 | return content; |
367 | } | 373 | } |
368 | /* Read all the chunks. */ | 374 | /* Read all the chunks. */ |
375 | int16_t blockAlign = 0; | ||
369 | while (!atEnd_Buffer(buf)) { | 376 | while (!atEnd_Buffer(buf)) { |
370 | readData_Buffer(buf, 4, magic); | 377 | readData_Buffer(buf, 4, magic); |
371 | const size_t size = read32_Stream(is); | 378 | const size_t size = read32_Stream(is); |
@@ -381,7 +388,7 @@ static iContentSpec contentSpec_Player_(const iPlayer *d) { | |||
381 | const int16_t numChannels = read16_Stream(is); | 388 | const int16_t numChannels = read16_Stream(is); |
382 | const int32_t freq = read32_Stream(is); | 389 | const int32_t freq = read32_Stream(is); |
383 | const uint32_t bytesPerSecond = readU32_Stream(is); | 390 | const uint32_t bytesPerSecond = readU32_Stream(is); |
384 | const int16_t blockAlign = read16_Stream(is); | 391 | blockAlign = read16_Stream(is); |
385 | const int16_t bitsPerSample = read16_Stream(is); | 392 | const int16_t bitsPerSample = read16_Stream(is); |
386 | const uint16_t extSize = (size == 18 ? readU16_Stream(is) : 0); | 393 | const uint16_t extSize = (size == 18 ? readU16_Stream(is) : 0); |
387 | iUnused(bytesPerSecond); | 394 | iUnused(bytesPerSecond); |
@@ -418,7 +425,8 @@ static iContentSpec contentSpec_Player_(const iPlayer *d) { | |||
418 | } | 425 | } |
419 | } | 426 | } |
420 | else if (memcmp(magic, "data", 4) == 0) { | 427 | else if (memcmp(magic, "data", 4) == 0) { |
421 | content.wavData = (iRanges){ pos_Stream(is), pos_Stream(is) + size }; | 428 | content.wavData = (iRanges){ pos_Stream(is), pos_Stream(is) + size }; |
429 | content.totalSamples = (uint64_t) size_Range(&content.wavData) / blockAlign; | ||
422 | break; | 430 | break; |
423 | } | 431 | } |
424 | else { | 432 | else { |
@@ -465,11 +473,16 @@ iBool isStarted_Player(const iPlayer *d) { | |||
465 | return d->device != 0; | 473 | return d->device != 0; |
466 | } | 474 | } |
467 | 475 | ||
468 | void setFormatHint_Player(iPlayer *d, const char *hint) { | 476 | iBool isPaused_Player(const iPlayer *d) { |
477 | if (!d->device) return iTrue; | ||
478 | return SDL_GetAudioDeviceStatus(d->device) == SDL_AUDIO_PAUSED; | ||
479 | } | ||
469 | 480 | ||
481 | void setFormatHint_Player(iPlayer *d, const char *hint) { | ||
470 | } | 482 | } |
471 | 483 | ||
472 | void updateSourceData_Player(iPlayer *d, const iBlock *data, enum iPlayerUpdate update) { | 484 | void updateSourceData_Player(iPlayer *d, const iBlock *data, enum iPlayerUpdate update) { |
485 | /* TODO: Add MIME as argument */ | ||
473 | iInputBuf *input = d->data; | 486 | iInputBuf *input = d->data; |
474 | lock_Mutex(&input->mtx); | 487 | lock_Mutex(&input->mtx); |
475 | switch (update) { | 488 | switch (update) { |
@@ -527,3 +540,13 @@ void stop_Player(iPlayer *d) { | |||
527 | d->decoder = NULL; | 540 | d->decoder = NULL; |
528 | } | 541 | } |
529 | } | 542 | } |
543 | |||
544 | float time_Player(const iPlayer *d) { | ||
545 | if (!d->decoder) return 0; | ||
546 | return (float) ((double) d->decoder->currentSample / (double) d->spec.freq); | ||
547 | } | ||
548 | |||
549 | float duration_Player(const iPlayer *d) { | ||
550 | if (!d->decoder) return 0; | ||
551 | return (float) ((double) d->decoder->totalSamples / (double) d->spec.freq); | ||
552 | } | ||
diff --git a/src/audio/player.h b/src/audio/player.h index 2138c556..5c17ef6c 100644 --- a/src/audio/player.h +++ b/src/audio/player.h | |||
@@ -41,3 +41,6 @@ void setPaused_Player (iPlayer *, iBool isPaused); | |||
41 | void stop_Player (iPlayer *); | 41 | void stop_Player (iPlayer *); |
42 | 42 | ||
43 | iBool isStarted_Player (const iPlayer *); | 43 | iBool isStarted_Player (const iPlayer *); |
44 | iBool isPaused_Player (const iPlayer *); | ||
45 | float time_Player (const iPlayer *); | ||
46 | float duration_Player (const iPlayer *); | ||
diff --git a/src/gmdocument.c b/src/gmdocument.c index ff0e019b..e2696085 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c | |||
@@ -570,7 +570,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
570 | pos.y += margin; | 570 | pos.y += margin; |
571 | run.bounds.pos = pos; | 571 | run.bounds.pos = pos; |
572 | run.bounds.size.x = d->size.x; | 572 | run.bounds.size.x = d->size.x; |
573 | run.bounds.size.y = 2 * lineHeight_Text(uiContent_FontId); | 573 | run.bounds.size.y = lineHeight_Text(uiContent_FontId) + 3 * gap_UI; |
574 | run.visBounds = run.bounds; | 574 | run.visBounds = run.bounds; |
575 | run.text = iNullRange; | 575 | run.text = iNullRange; |
576 | run.color = 0; | 576 | run.color = 0; |
diff --git a/src/media.c b/src/media.c index b72ec32c..ddf5d45f 100644 --- a/src/media.c +++ b/src/media.c | |||
@@ -266,3 +266,11 @@ iBool audioInfo_Media(const iMedia *d, iMediaId audioId, iGmAudioInfo *info_out) | |||
266 | iZap(*info_out); | 266 | iZap(*info_out); |
267 | return iFalse; | 267 | return iFalse; |
268 | } | 268 | } |
269 | |||
270 | iPlayer *audioPlayer_Media(const iMedia *d, iMediaId audioId) { | ||
271 | if (audioId > 0 && audioId <= size_PtrArray(&d->audio)) { | ||
272 | const iGmAudio *audio = constAt_PtrArray(&d->audio, audioId - 1); | ||
273 | return audio->player; | ||
274 | } | ||
275 | return NULL; | ||
276 | } | ||
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 | |||
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | 21 | SOFTWARE, 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 | ||
336 | static void addVisibleLink_DocumentWidget_(void *context, const iGmRun *run) { | 341 | static 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 | ||
445 | static 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 | |||
437 | static void updateVisible_DocumentWidget_(iDocumentWidget *d) { | 457 | static 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 | ||
2264 | iDeclareType(PlayerUI) | ||
2265 | |||
2266 | struct Impl_PlayerUI { | ||
2267 | const iPlayer *player; | ||
2268 | iRect bounds; | ||
2269 | iRect playPauseRect; | ||
2270 | iRect rewindRect; | ||
2271 | iRect scrubberRect; | ||
2272 | iRect menuRect; | ||
2273 | }; | ||
2274 | |||
2275 | static 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 | |||
2285 | static 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 | |||
2302 | static 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 | |||
2328 | static 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 | |||
2365 | static 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 | |||
2242 | static void draw_DocumentWidget_(const iDocumentWidget *d) { | 2377 | static 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)) { |