summaryrefslogtreecommitdiff
path: root/src/ui/documentwidget.c
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-10-08 08:22:50 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-10-08 08:22:50 +0300
commitcf713705fbc53e262b440f128b3a6dc65bea3b78 (patch)
tree12504042b00009f45199e5e9e4310fa4546b8b5a /src/ui/documentwidget.c
parentc4aa47eddd21c51173d4400e44e06f32d2080d2d (diff)
Player: Play/pause button in the UI
Diffstat (limited to 'src/ui/documentwidget.c')
-rw-r--r--src/ui/documentwidget.c243
1 files changed, 139 insertions, 104 deletions
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 8b769cf8..81d7e879 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -1110,6 +1110,7 @@ static const char *sourceLoc_DocumentWidget_(const iDocumentWidget *d, iInt2 pos
1110} 1110}
1111 1111
1112iDeclareType(MiddleRunParams) 1112iDeclareType(MiddleRunParams)
1113
1113struct Impl_MiddleRunParams { 1114struct Impl_MiddleRunParams {
1114 int midY; 1115 int midY;
1115 const iGmRun *closest; 1116 const iGmRun *closest;
@@ -1591,6 +1592,140 @@ static size_t visibleLinkOrdinal_DocumentWidget_(const iDocumentWidget *d, iGmLi
1591 return iInvalidPos; 1592 return iInvalidPos;
1592} 1593}
1593 1594
1595iDeclareType(PlayerUI)
1596
1597struct Impl_PlayerUI {
1598 const iPlayer *player;
1599 iRect bounds;
1600 iRect playPauseRect;
1601 iRect rewindRect;
1602 iRect scrubberRect;
1603 iRect menuRect;
1604};
1605
1606static void init_PlayerUI(iPlayerUI *d, const iPlayer *player, iRect bounds) {
1607 d->player = player;
1608 d->bounds = bounds;
1609 const int height = height_Rect(bounds);
1610 d->playPauseRect = (iRect){ addX_I2(topLeft_Rect(bounds), gap_UI / 2), init1_I2(height) };
1611 d->rewindRect = (iRect){ topRight_Rect(d->playPauseRect), init1_I2(height) };
1612 d->menuRect = (iRect){ addX_I2(topRight_Rect(bounds), -height - gap_UI / 2), init1_I2(height) };
1613 d->scrubberRect = initCorners_Rect(topRight_Rect(d->rewindRect), bottomLeft_Rect(d->menuRect));
1614}
1615
1616static void drawPlayerButton_(iPaint *p, iRect rect, const char *label) {
1617 const iInt2 mouse = mouseCoord_Window(get_Window());
1618 const iBool isHover = contains_Rect(rect, mouse);
1619 const iBool isPressed = isHover && (SDL_GetMouseState(NULL, NULL) & SDL_BUTTON_LEFT) != 0;
1620 const int frame = (isPressed ? uiTextCaution_ColorId : isHover ? uiHeading_ColorId : uiAnnotation_ColorId);
1621 iRect frameRect = shrunk_Rect(rect, init_I2(gap_UI / 2, gap_UI));
1622 drawRect_Paint(p, frameRect, frame);
1623 if (isPressed) {
1624 fillRect_Paint(
1625 p,
1626 adjusted_Rect(shrunk_Rect(frameRect, divi_I2(gap2_UI, 2)), zero_I2(), one_I2()),
1627 frame);
1628 }
1629 const int fg = isPressed ? uiBackground_ColorId : frame;
1630 drawCentered_Text(uiContent_FontId, frameRect, iTrue, fg, "%s", label);
1631}
1632
1633static int drawSevenSegmentTime_(iInt2 pos, int color, int align, int seconds) { /* returns width */
1634 const uint32_t sevenSegmentDigit = 0x1fbf0;
1635 const int hours = seconds / 3600;
1636 const int mins = (seconds / 60) % 60;
1637 const int secs = seconds % 60;
1638 const int font = uiLabel_FontId;
1639 iString num;
1640 init_String(&num);
1641 if (hours) {
1642 appendChar_String(&num, sevenSegmentDigit + (hours % 10));
1643 appendChar_String(&num, ':');
1644 }
1645 appendChar_String(&num, sevenSegmentDigit + (mins / 10) % 10);
1646 appendChar_String(&num, sevenSegmentDigit + (mins % 10));
1647 appendChar_String(&num, ':');
1648 appendChar_String(&num, sevenSegmentDigit + (secs / 10) % 10);
1649 appendChar_String(&num, sevenSegmentDigit + (secs % 10));
1650 iInt2 size = advanceRange_Text(font, range_String(&num));
1651 if (align == right_Alignment) {
1652 pos.x -= size.x;
1653 }
1654 drawRange_Text(font, pos, color, range_String(&num));
1655 deinit_String(&num);
1656 return size.x;
1657}
1658
1659static void draw_PlayerUI(iPlayerUI *d, iPaint *p) {
1660 const int playerBackground_ColorId = uiBackground_ColorId;
1661 const int playerFrame_ColorId = uiSeparator_ColorId;
1662 fillRect_Paint(p, d->bounds, playerBackground_ColorId);
1663 drawRect_Paint(p, d->bounds, playerFrame_ColorId);
1664 drawPlayerButton_(p, d->playPauseRect, isPaused_Player(d->player) ? "\U0001f782" : "\u23f8");
1665 drawPlayerButton_(p, d->rewindRect, "\u23ee");
1666 drawPlayerButton_(p, d->menuRect, "\U0001d362");
1667 const int hgt = lineHeight_Text(uiLabel_FontId);
1668 const int yMid = mid_Rect(d->scrubberRect).y;
1669 const float playTime = time_Player(d->player);
1670 const float totalTime = duration_Player(d->player);
1671 const int bright = uiHeading_ColorId;
1672 const int dim = uiAnnotation_ColorId;
1673 int leftWidth = drawSevenSegmentTime_(
1674 init_I2(left_Rect(d->scrubberRect) + 2 * gap_UI, yMid - hgt / 2),
1675 isPaused_Player(d->player) ? dim : bright,
1676 left_Alignment,
1677 iRound(playTime));
1678 int rightWidth = drawSevenSegmentTime_(
1679 init_I2(right_Rect(d->scrubberRect) - 2 * gap_UI, yMid - hgt / 2),
1680 dim,
1681 right_Alignment,
1682 iRound(totalTime));
1683 /* Scrubber. */
1684 const int s1 = left_Rect(d->scrubberRect) + leftWidth + 6 * gap_UI;
1685 const int s2 = right_Rect(d->scrubberRect) - rightWidth - 6 * gap_UI;
1686 const float normPos = totalTime > 0 ? playTime / totalTime : 0.0f;
1687 const int part = (s2 - s1) * normPos;
1688 const int scrubMax = (s2 - s1) * streamProgress_Player(d->player);
1689 drawHLine_Paint(p, init_I2(s1, yMid), part, bright);
1690 drawHLine_Paint(p, init_I2(s1 + part, yMid), scrubMax - part, dim);
1691 draw_Text(uiLabel_FontId,
1692 init_I2(s1 * (1.0f - normPos) + s2 * normPos - hgt / 3, yMid - hgt / 2),
1693 bright,
1694 "\u23fa");
1695}
1696
1697static iRect audioPlayerRect_DocumentWidget_(const iDocumentWidget *d, const iGmRun *run) {
1698 const iRect docBounds = documentBounds_DocumentWidget_(d);
1699 return moved_Rect(run->bounds, addY_I2(topLeft_Rect(docBounds), -d->scrollY));
1700}
1701
1702static iBool processAudioPlayerEvents_DocumentWidget_(iDocumentWidget *d, const SDL_Event *ev) {
1703 if (ev->type != SDL_MOUSEBUTTONDOWN && ev->type != SDL_MOUSEBUTTONUP &&
1704 ev->type != SDL_MOUSEMOTION) {
1705 return iFalse;
1706 }
1707 const iInt2 mouse = init_I2(ev->button.x, ev->button.y);
1708 iConstForEach(PtrArray, i, &d->visiblePlayers) {
1709 const iGmRun *run = i.ptr;
1710 const iRect rect = audioPlayerRect_DocumentWidget_(d, run);
1711 if (contains_Rect(rect, mouse)) {
1712 if (ev->type == SDL_MOUSEBUTTONDOWN || ev->type == SDL_MOUSEMOTION) {
1713 refresh_Widget(d);
1714 return iTrue;
1715 }
1716 iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), run->audioId);
1717 iPlayerUI ui;
1718 init_PlayerUI(&ui, plr, rect);
1719 if (contains_Rect(ui.playPauseRect, mouse)) {
1720 setPaused_Player(plr, !isPaused_Player(plr));
1721 animatePlayingAudio_DocumentWidget_(d);
1722 return iTrue;
1723 }
1724 }
1725 }
1726 return iFalse;
1727}
1728
1594static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *ev) { 1729static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *ev) {
1595 iWidget *w = as_Widget(d); 1730 iWidget *w = as_Widget(d);
1596 if (ev->type == SDL_USEREVENT && ev->user.code == command_UserEventCode) { 1731 if (ev->type == SDL_USEREVENT && ev->user.code == command_UserEventCode) {
@@ -1813,6 +1948,9 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
1813 processContextMenuEvent_Widget(d->menu, ev, d->hoverLink = NULL); 1948 processContextMenuEvent_Widget(d->menu, ev, d->hoverLink = NULL);
1814 } 1949 }
1815 } 1950 }
1951 if (processAudioPlayerEvents_DocumentWidget_(d, ev)) {
1952 return iTrue;
1953 }
1816 switch (processEvent_Click(&d->click, ev)) { 1954 switch (processEvent_Click(&d->click, ev)) {
1817 case started_ClickResult: 1955 case started_ClickResult:
1818 d->selecting = iFalse; 1956 d->selecting = iFalse;
@@ -2292,114 +2430,11 @@ static void drawSideElements_DocumentWidget_(const iDocumentWidget *d) {
2292 unsetClip_Paint(&p); 2430 unsetClip_Paint(&p);
2293} 2431}
2294 2432
2295iDeclareType(PlayerUI)
2296
2297struct Impl_PlayerUI {
2298 const iPlayer *player;
2299 iRect bounds;
2300 iRect playPauseRect;
2301 iRect rewindRect;
2302 iRect scrubberRect;
2303 iRect menuRect;
2304};
2305
2306static void init_PlayerUI(iPlayerUI *d, const iPlayer *player, iRect bounds) {
2307 d->player = player;
2308 d->bounds = bounds;
2309 const int height = height_Rect(bounds);
2310 d->playPauseRect = (iRect){ addX_I2(topLeft_Rect(bounds), gap_UI / 2), init1_I2(height) };
2311 d->rewindRect = (iRect){ topRight_Rect(d->playPauseRect), init1_I2(height) };
2312 d->menuRect = (iRect){ addX_I2(topRight_Rect(bounds), -height - gap_UI / 2), init1_I2(height) };
2313 d->scrubberRect = initCorners_Rect(topRight_Rect(d->rewindRect), bottomLeft_Rect(d->menuRect));
2314}
2315
2316static void drawPlayerButton_(iPaint *p, iRect rect, const char *label) {
2317 const iInt2 mouse = mouseCoord_Window(get_Window());
2318 const iBool isHover = contains_Rect(rect, mouse);
2319 const iBool isPressed = isHover && (SDL_GetMouseState(NULL, NULL) & SDL_BUTTON_LEFT) != 0;
2320 const int frame = (isPressed ? uiTextCaution_ColorId : isHover ? uiHeading_ColorId : uiAnnotation_ColorId);
2321 iRect frameRect = shrunk_Rect(rect, init_I2(gap_UI / 2, gap_UI));
2322 drawRect_Paint(p, frameRect, frame);
2323 if (isPressed) {
2324 fillRect_Paint(
2325 p,
2326 adjusted_Rect(shrunk_Rect(frameRect, divi_I2(gap2_UI, 2)), zero_I2(), one_I2()),
2327 frame);
2328 }
2329 const int fg = isPressed ? uiBackground_ColorId : frame;
2330 drawCentered_Text(uiContent_FontId, frameRect, iTrue, fg, "%s", label);
2331}
2332
2333static int drawSevenSegmentTime_(iInt2 pos, int color, int align, int seconds) { /* returns width */
2334 const uint32_t sevenSegmentDigit = 0x1fbf0;
2335 const int hours = seconds / 3600;
2336 const int mins = (seconds / 60) % 60;
2337 const int secs = seconds % 60;
2338 const int font = uiLabel_FontId;
2339 iString num;
2340 init_String(&num);
2341 if (hours) {
2342 appendChar_String(&num, sevenSegmentDigit + (hours % 10));
2343 appendChar_String(&num, ':');
2344 }
2345 appendChar_String(&num, sevenSegmentDigit + (mins / 10) % 10);
2346 appendChar_String(&num, sevenSegmentDigit + (mins % 10));
2347 appendChar_String(&num, ':');
2348 appendChar_String(&num, sevenSegmentDigit + (secs / 10) % 10);
2349 appendChar_String(&num, sevenSegmentDigit + (secs % 10));
2350 iInt2 size = advanceRange_Text(font, range_String(&num));
2351 if (align == right_Alignment) {
2352 pos.x -= size.x;
2353 }
2354 drawRange_Text(font, pos, color, range_String(&num));
2355 deinit_String(&num);
2356 return size.x;
2357}
2358
2359static void draw_PlayerUI(iPlayerUI *d, iPaint *p) {
2360 const int playerBackground_ColorId = uiBackground_ColorId;
2361 const int playerFrame_ColorId = uiSeparator_ColorId;
2362 fillRect_Paint(p, d->bounds, playerBackground_ColorId);
2363 drawRect_Paint(p, d->bounds, playerFrame_ColorId);
2364 drawPlayerButton_(p, d->playPauseRect, isPaused_Player(d->player) ? "\U0001f782" : "\u23f8");
2365 drawPlayerButton_(p, d->rewindRect, "\u23ee");
2366 drawPlayerButton_(p, d->menuRect, "\U0001d362");
2367 const int hgt = lineHeight_Text(uiLabel_FontId);
2368 const int yMid = mid_Rect(d->scrubberRect).y;
2369 const float playTime = time_Player(d->player);
2370 const float totalTime = duration_Player(d->player);
2371 const int bright = uiHeading_ColorId;
2372 const int dim = uiAnnotation_ColorId;
2373 int leftWidth = drawSevenSegmentTime_(
2374 init_I2(left_Rect(d->scrubberRect) + 2 * gap_UI, yMid - hgt / 2),
2375 isPaused_Player(d->player) ? dim : bright,
2376 left_Alignment,
2377 iRound(playTime));
2378 int rightWidth = drawSevenSegmentTime_(
2379 init_I2(right_Rect(d->scrubberRect) - 2 * gap_UI, yMid - hgt / 2),
2380 dim,
2381 right_Alignment,
2382 iRound(totalTime));
2383 /* Scrubber. */
2384 const int s1 = left_Rect(d->scrubberRect) + leftWidth + 6 * gap_UI;
2385 const int s2 = right_Rect(d->scrubberRect) - rightWidth - 6 * gap_UI;
2386 const float normPos = totalTime > 0 ? playTime / totalTime : 0.0f;
2387 const int part = (s2 - s1) * normPos;
2388 const int scrubMax = (s2 - s1) * streamProgress_Player(d->player);
2389 drawHLine_Paint(p, init_I2(s1, yMid), part, bright);
2390 drawHLine_Paint(p, init_I2(s1 + part, yMid), scrubMax - part, dim);
2391 draw_Text(uiLabel_FontId,
2392 init_I2(s1 * (1.0f - normPos) + s2 * normPos - hgt / 3, yMid - hgt / 2),
2393 bright,
2394 "\u23fa");
2395}
2396
2397static void drawAudioPlayers_DocumentWidget_(const iDocumentWidget *d, iPaint *p) { 2433static void drawAudioPlayers_DocumentWidget_(const iDocumentWidget *d, iPaint *p) {
2398 const iRect docBounds = documentBounds_DocumentWidget_(d);
2399 iConstForEach(PtrArray, i, &d->visiblePlayers) { 2434 iConstForEach(PtrArray, i, &d->visiblePlayers) {
2400 const iGmRun * run = i.ptr; 2435 const iGmRun * run = i.ptr;
2401 const iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), run->audioId); 2436 const iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), run->audioId);
2402 const iRect rect = moved_Rect(run->bounds, addY_I2(topLeft_Rect(docBounds), -d->scrollY)); 2437 const iRect rect = audioPlayerRect_DocumentWidget_(d, run);
2403 iPlayerUI ui; 2438 iPlayerUI ui;
2404 init_PlayerUI(&ui, plr, rect); 2439 init_PlayerUI(&ui, plr, rect);
2405 draw_PlayerUI(&ui, p); 2440 draw_PlayerUI(&ui, p);