diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-10-14 19:12:11 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-10-14 19:12:11 +0300 |
commit | 6b589f349f5fa459d25d865a65d11e242232b7a5 (patch) | |
tree | 6f477867e4815b3bcc2dc90fbf2c0e6fdcb7427e /src/ui/playerui.c | |
parent | 72a2736572c31bf17ef36e422d35c3975d41e470 (diff) |
Player: Volume adjustment UI
Diffstat (limited to 'src/ui/playerui.c')
-rw-r--r-- | src/ui/playerui.c | 184 |
1 files changed, 184 insertions, 0 deletions
diff --git a/src/ui/playerui.c b/src/ui/playerui.c new file mode 100644 index 00000000..fadbc2da --- /dev/null +++ b/src/ui/playerui.c | |||
@@ -0,0 +1,184 @@ | |||
1 | /* Copyright 2020 Jaakko Keränen <jaakko.keranen@iki.fi> | ||
2 | |||
3 | Redistribution and use in source and binary forms, with or without | ||
4 | modification, are permitted provided that the following conditions are met: | ||
5 | |||
6 | 1. Redistributions of source code must retain the above copyright notice, this | ||
7 | list of conditions and the following disclaimer. | ||
8 | 2. Redistributions in binary form must reproduce the above copyright notice, | ||
9 | this list of conditions and the following disclaimer in the documentation | ||
10 | and/or other materials provided with the distribution. | ||
11 | |||
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
15 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | ||
16 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
17 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
18 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | ||
19 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
20 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | ||
22 | |||
23 | #include "playerui.h" | ||
24 | #include "audio/player.h" | ||
25 | #include "paint.h" | ||
26 | #include "util.h" | ||
27 | |||
28 | static const char *volumeChar_(float volume) { | ||
29 | if (volume <= 0) { | ||
30 | return "\U0001f507"; | ||
31 | } | ||
32 | if (volume < 0.4f) { | ||
33 | return "\U0001f508"; | ||
34 | } | ||
35 | if (volume < 0.8f) { | ||
36 | return "\U0001f509"; | ||
37 | } | ||
38 | return "\U0001f50a"; | ||
39 | } | ||
40 | |||
41 | void init_PlayerUI(iPlayerUI *d, const iPlayer *player, iRect bounds) { | ||
42 | d->player = player; | ||
43 | d->bounds = bounds; | ||
44 | const int height = height_Rect(bounds); | ||
45 | d->playPauseRect = (iRect){ addX_I2(topLeft_Rect(bounds), gap_UI / 2), init_I2(3 * height / 2, height) }; | ||
46 | d->rewindRect = (iRect){ topRight_Rect(d->playPauseRect), init1_I2(height) }; | ||
47 | d->menuRect = (iRect){ addX_I2(topRight_Rect(bounds), -height - gap_UI / 2), init1_I2(height) }; | ||
48 | d->volumeRect = (iRect){ addX_I2(topLeft_Rect(d->menuRect), -height), init1_I2(height) }; | ||
49 | d->volumeAdjustRect = d->volumeRect; | ||
50 | adjustEdges_Rect(&d->volumeAdjustRect, 0, 0, 0, -35 * gap_UI); | ||
51 | d->scrubberRect = initCorners_Rect(topRight_Rect(d->rewindRect), bottomLeft_Rect(d->volumeRect)); | ||
52 | /* Volume slider. */ { | ||
53 | d->volumeSlider = shrunk_Rect(d->volumeAdjustRect, init_I2(gap_UI / 2, gap_UI)); | ||
54 | adjustEdges_Rect(&d->volumeSlider, 0, -width_Rect(d->volumeRect) - 2 * gap_UI, 0, 5 * gap_UI); | ||
55 | } | ||
56 | } | ||
57 | |||
58 | static void drawPlayerButton_(iPaint *p, iRect rect, const char *label, int font) { | ||
59 | const iInt2 mouse = mouseCoord_Window(get_Window()); | ||
60 | const iBool isHover = contains_Rect(rect, mouse); | ||
61 | const iBool isPressed = isHover && (SDL_GetMouseState(NULL, NULL) & SDL_BUTTON_LEFT) != 0; | ||
62 | const int frame = (isPressed ? uiTextCaution_ColorId : isHover ? uiHeading_ColorId : uiAnnotation_ColorId); | ||
63 | iRect frameRect = shrunk_Rect(rect, init_I2(gap_UI / 2, gap_UI)); | ||
64 | drawRect_Paint(p, frameRect, frame); | ||
65 | if (isPressed) { | ||
66 | fillRect_Paint( | ||
67 | p, | ||
68 | adjusted_Rect(shrunk_Rect(frameRect, divi_I2(gap2_UI, 2)), zero_I2(), one_I2()), | ||
69 | frame); | ||
70 | } | ||
71 | const int fg = isPressed ? (permanent_ColorId | uiBackground_ColorId) : uiHeading_ColorId; | ||
72 | drawCentered_Text(font, frameRect, iTrue, fg, "%s", label); | ||
73 | } | ||
74 | |||
75 | static int drawSevenSegmentTime_(iInt2 pos, int color, int align, int seconds) { /* returns width */ | ||
76 | const uint32_t sevenSegmentDigit = 0x1fbf0; | ||
77 | const int hours = seconds / 3600; | ||
78 | const int mins = (seconds / 60) % 60; | ||
79 | const int secs = seconds % 60; | ||
80 | const int font = uiLabel_FontId; | ||
81 | iString num; | ||
82 | init_String(&num); | ||
83 | if (hours) { | ||
84 | appendChar_String(&num, sevenSegmentDigit + (hours % 10)); | ||
85 | appendChar_String(&num, ':'); | ||
86 | } | ||
87 | appendChar_String(&num, sevenSegmentDigit + (mins / 10) % 10); | ||
88 | appendChar_String(&num, sevenSegmentDigit + (mins % 10)); | ||
89 | appendChar_String(&num, ':'); | ||
90 | appendChar_String(&num, sevenSegmentDigit + (secs / 10) % 10); | ||
91 | appendChar_String(&num, sevenSegmentDigit + (secs % 10)); | ||
92 | iInt2 size = advanceRange_Text(font, range_String(&num)); | ||
93 | if (align == right_Alignment) { | ||
94 | pos.x -= size.x; | ||
95 | } | ||
96 | drawRange_Text(font, pos, color, range_String(&num)); | ||
97 | deinit_String(&num); | ||
98 | return size.x; | ||
99 | } | ||
100 | |||
101 | void draw_PlayerUI(iPlayerUI *d, iPaint *p) { | ||
102 | const int playerBackground_ColorId = uiBackground_ColorId; | ||
103 | const int playerFrame_ColorId = uiSeparator_ColorId; | ||
104 | const iBool isAdjusting = (flags_Player(d->player) & adjustingVolume_PlayerFlag) != 0; | ||
105 | fillRect_Paint(p, d->bounds, playerBackground_ColorId); | ||
106 | drawRect_Paint(p, d->bounds, playerFrame_ColorId); | ||
107 | drawPlayerButton_(p, | ||
108 | d->playPauseRect, | ||
109 | isPaused_Player(d->player) ? "\U0001f782" : "\u23f8", | ||
110 | uiContent_FontId); | ||
111 | drawPlayerButton_(p, d->rewindRect, "\u23ee", uiContent_FontId); | ||
112 | drawPlayerButton_(p, d->menuRect, "\U0001d362", uiContent_FontId); | ||
113 | if (!isAdjusting) { | ||
114 | drawPlayerButton_( | ||
115 | p, d->volumeRect, volumeChar_(volume_Player(d->player)), uiContentSymbols_FontId); | ||
116 | } | ||
117 | const int hgt = lineHeight_Text(uiLabel_FontId); | ||
118 | const int yMid = mid_Rect(d->scrubberRect).y; | ||
119 | const float playTime = time_Player(d->player); | ||
120 | const float totalTime = duration_Player(d->player); | ||
121 | const int bright = uiHeading_ColorId; | ||
122 | const int dim = uiAnnotation_ColorId; | ||
123 | int leftWidth = drawSevenSegmentTime_( | ||
124 | init_I2(left_Rect(d->scrubberRect) + 2 * gap_UI, yMid - hgt / 2), | ||
125 | isPaused_Player(d->player) ? dim : bright, | ||
126 | left_Alignment, | ||
127 | iRound(playTime)); | ||
128 | int rightWidth = 0; | ||
129 | if (totalTime > 0) { | ||
130 | rightWidth = | ||
131 | drawSevenSegmentTime_(init_I2(right_Rect(d->scrubberRect) - 2 * gap_UI, yMid - hgt / 2), | ||
132 | dim, | ||
133 | right_Alignment, | ||
134 | iRound(totalTime)); | ||
135 | } | ||
136 | /* Scrubber. */ | ||
137 | const int s1 = left_Rect(d->scrubberRect) + leftWidth + 6 * gap_UI; | ||
138 | const int s2 = right_Rect(d->scrubberRect) - rightWidth - 6 * gap_UI; | ||
139 | const float normPos = totalTime > 0 ? playTime / totalTime : 0.0f; | ||
140 | const int part = (s2 - s1) * normPos; | ||
141 | const int scrubMax = (s2 - s1) * streamProgress_Player(d->player); | ||
142 | drawHLine_Paint(p, init_I2(s1, yMid), part, bright); | ||
143 | drawHLine_Paint(p, init_I2(s1 + part, yMid), scrubMax - part, dim); | ||
144 | const char *dot = "\u23fa"; | ||
145 | const int dotWidth = advance_Text(uiLabel_FontId, dot).x; | ||
146 | draw_Text(uiLabel_FontId, | ||
147 | init_I2(s1 * (1.0f - normPos) + s2 * normPos - dotWidth / 2, yMid - hgt / 2), | ||
148 | bright, | ||
149 | dot); | ||
150 | /* Volume adjustment. */ | ||
151 | if (isAdjusting) { | ||
152 | const iInt2 mouse = mouseCoord_Window(get_Window()); | ||
153 | const iBool isHover = contains_Rect(d->volumeRect, mouse) && | ||
154 | ~flags_Player(d->player) & volumeGrabbed_PlayerFlag; | ||
155 | const iBool isPressed = (SDL_GetMouseState(NULL, NULL) & SDL_BUTTON_LEFT) != 0; | ||
156 | iRect adjRect = shrunk_Rect(d->volumeAdjustRect, init_I2(gap_UI / 2, gap_UI)); | ||
157 | fillRect_Paint(p, adjRect, playerBackground_ColorId); | ||
158 | drawRect_Paint(p, adjRect, bright); | ||
159 | if (isHover) { | ||
160 | fillRect_Paint( | ||
161 | p, | ||
162 | shrunk_Rect(d->volumeRect, init_I2(gap_UI / 2 + gap_UI / 2, 3 * gap_UI / 2)), | ||
163 | isPressed ? uiTextCaution_ColorId : bright); | ||
164 | } | ||
165 | drawCentered_Text(uiContentSymbols_FontId, | ||
166 | d->volumeRect, | ||
167 | iTrue, | ||
168 | isHover ? playerBackground_ColorId : bright, | ||
169 | volumeChar_(volume_Player(d->player))); | ||
170 | const int volColor = | ||
171 | flags_Player(d->player) & volumeGrabbed_PlayerFlag ? uiTextCaution_ColorId : bright; | ||
172 | const int volPart = volume_Player(d->player) * width_Rect(d->volumeSlider); | ||
173 | const iInt2 volPos = init_I2(left_Rect(d->volumeSlider), mid_Rect(d->volumeSlider).y); | ||
174 | drawHLine_Paint(p, volPos, volPart, volColor); | ||
175 | drawHLine_Paint(p, | ||
176 | addX_I2(volPos, volPart), | ||
177 | width_Rect(d->volumeSlider) - volPart, | ||
178 | dim); | ||
179 | draw_Text(uiLabel_FontId, | ||
180 | init_I2(left_Rect(d->volumeSlider) + volPart - dotWidth / 2, yMid - hgt / 2), | ||
181 | volColor, | ||
182 | dot); | ||
183 | } | ||
184 | } | ||