diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-10-07 12:31:31 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-10-07 12:31:31 +0300 |
commit | 1ffd161fa550d5334df7ee1aaa16bc369a1324b4 (patch) | |
tree | f2ccf418fc73e34852e7eee9ece330a31c547b74 /src/ui | |
parent | 403d8fc06fda157b134f96328f98b9305509b5a3 (diff) |
Drawing an audio player UI
Diffstat (limited to 'src/ui')
-rw-r--r-- | src/ui/documentwidget.c | 164 |
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 | |||
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)) { |