1 /**
2 * @file
3 * @todo add getter/setter to cleanup access to extradata from cl_*.c files (check "u.text.")
4 */
5
6 /*
7 Copyright (C) 2002-2013 UFO: Alien Invasion.
8
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License
11 as published by the Free Software Foundation; either version 2
12 of the License, or (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17
18 See the GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23
24 */
25
26 #include "../ui_main.h"
27 #include "../ui_internal.h"
28 #include "../ui_font.h"
29 #include "../ui_actions.h"
30 #include "../ui_parse.h"
31 #include "../ui_render.h"
32 #include "../ui_sprite.h"
33 #include "ui_node_text.h"
34 #include "ui_node_messagelist.h"
35 #include "ui_node_abstractnode.h"
36
37 #include "../../client.h"
38 #include "../../../shared/parse.h"
39
40 #define EXTRADATA(node) UI_EXTRADATA(node, abstractScrollableExtraData_t)
41 #define EXTRADATACONST(node) UI_EXTRADATACONST(node, abstractScrollableExtraData_t)
42
43 /** @todo use the font height? */
44 static const int LINEHEIGHT = 20;
45
46 static const int DATETIME_COLUUI_SIZE = 180;
47
48 /* Used for drag&drop-like scrolling */
49 static int mouseScrollX;
50 static int mouseScrollY;
51
52 /* Russian timestamp (with UTF-8) is 23 bytes long */
53 #define TIMESTAMP_TEXT 24
54 typedef struct uiMessageListNodeMessage_s {
55 char title[MAX_VAR];
56 char timestamp[TIMESTAMP_TEXT];
57 char* text;
58 date_t date;
59 const char* iconName;
60 int lineUsed; /**< used by the node to cache the number of lines need (often =1) */
61 struct uiMessageListNodeMessage_s* next;
62 } uiMessageListNodeMessage_t;
63
64 /** @todo implement this on a per-node basis */
65 static uiMessageListNodeMessage_t* messageStack;
66
UI_MessageGetStack(void)67 struct uiMessageListNodeMessage_s* UI_MessageGetStack (void)
68 {
69 return messageStack;
70 }
71
UI_MessageResetStack(void)72 void UI_MessageResetStack (void)
73 {
74 messageStack = nullptr;
75 }
76
UI_MessageAddStack(struct uiMessageListNodeMessage_s * message)77 void UI_MessageAddStack (struct uiMessageListNodeMessage_s* message)
78 {
79 message->next = messageStack;
80 messageStack = message;
81 }
82
83 /**
84 * @return Number of lines need to display this message
85 */
UI_MessageGetLines(const uiNode_t * node,uiMessageListNodeMessage_t * message,const char * fontID,int width)86 static int UI_MessageGetLines (const uiNode_t* node, uiMessageListNodeMessage_t* message, const char* fontID, int width)
87 {
88 const int column1 = DATETIME_COLUUI_SIZE;
89 const int column2 = width - DATETIME_COLUUI_SIZE - node->padding;
90 int lines1;
91 int lines2;
92 R_FontTextSize(fontID, message->timestamp, column1, LONGLINES_WRAP, nullptr, nullptr, &lines1, nullptr);
93 R_FontTextSize(fontID, message->text, column2, LONGLINES_WRAP, nullptr, nullptr, &lines2, nullptr);
94 return std::max(lines1, lines2);
95 }
96
97 static char* lastDate;
98
99 /**
100 * @todo do not hard code icons
101 * @todo cache icon result
102 */
UI_MessageGetIcon(const uiMessageListNodeMessage_t * message)103 static uiSprite_t* UI_MessageGetIcon (const uiMessageListNodeMessage_t* message)
104 {
105 const char* iconName = message->iconName;
106 if (Q_strnull(iconName))
107 iconName = "icons/message_info";
108
109 return UI_GetSpriteByName(message->iconName);
110 }
111
UI_MessageDraw(const uiNode_t * node,uiMessageListNodeMessage_t * message,const char * fontID,int x,int y,int width,int * screenLines)112 static void UI_MessageDraw (const uiNode_t* node, uiMessageListNodeMessage_t* message, const char* fontID, int x, int y, int width, int* screenLines)
113 {
114 const int column1 = DATETIME_COLUUI_SIZE;
115 const int column2 = width - DATETIME_COLUUI_SIZE - node->padding;
116 int lines1 = *screenLines;
117 int lines2 = *screenLines;
118
119 /* also display the first date on wraped message we only see the end */
120 if (lines1 < 0)
121 lines1 = 0;
122
123 /* display the date */
124 if (lastDate == nullptr || !Q_streq(lastDate, message->timestamp)) {
125 R_Color(node->color);
126 UI_DrawString(fontID, ALIGN_UL, x, y, x, column1, LINEHEIGHT, message->timestamp, EXTRADATACONST(node).scrollY.viewSize, 0, &lines1, true, LONGLINES_WRAP);
127 R_Color(nullptr);
128 }
129
130 x += DATETIME_COLUUI_SIZE + node->padding;
131
132 /* identify the begin of a message with a mark */
133 if (lines2 >= 0) {
134 const uiSprite_t* icon = UI_MessageGetIcon(message);
135 R_Color(nullptr);
136 UI_DrawSpriteInBox(false, icon, SPRITE_STATUS_NORMAL, x - 25, y + LINEHEIGHT * lines2 - 1, 19, 19);
137 }
138
139 /* draw the message */
140 R_Color(node->color);
141 UI_DrawString(fontID, ALIGN_UL, x, y, x, column2, LINEHEIGHT, message->text, EXTRADATACONST(node).scrollY.viewSize, 0, &lines2, true, LONGLINES_WRAP);
142 R_Color(nullptr);
143 *screenLines = std::max(lines1, lines2);
144 lastDate = message->timestamp;
145 }
146
147 /**
148 * @brief Draws the messagesystem node
149 * @param[in] node The context node
150 */
draw(uiNode_t * node)151 void uiMessageListNode::draw (uiNode_t* node)
152 {
153 uiMessageListNodeMessage_t* message;
154 int screenLines;
155 const char* font = UI_GetFontFromNode(node);
156 vec2_t pos;
157 int x, y, width;
158 int defaultHeight;
159 int lineNumber = 0;
160 int posY;
161
162 /* #define AUTOSCROLL */ /**< if newer messages are on top, autoscroll is not need */
163 #ifdef AUTOSCROLL
164 bool autoscroll;
165 #endif
166 UI_GetNodeAbsPos(node, pos);
167
168 defaultHeight = LINEHEIGHT;
169
170 #ifdef AUTOSCROLL
171 autoscroll = (EXTRADATA(node).scrollY.viewPos + EXTRADATA(node).scrollY.viewSize == EXTRADATA(node).scrollY.fullSize)
172 || (EXTRADATA(node).scrollY.fullSize < EXTRADATA(node).scrollY.viewSize);
173 #endif
174
175 /* text box */
176 x = pos[0] + node->padding;
177 y = pos[1] + node->padding;
178 width = node->box.size[0] - node->padding - node->padding;
179
180 /* update message cache */
181 if (isSizeChange(node)) {
182 /* recompute all line size */
183 message = messageStack;
184 while (message) {
185 message->lineUsed = UI_MessageGetLines(node, message, font, width);
186 lineNumber += message->lineUsed;
187 message = message->next;
188 }
189 } else {
190 /* only check unvalidated messages */
191 message = messageStack;
192 while (message) {
193 if (message->lineUsed == 0)
194 message->lineUsed = UI_MessageGetLines(node, message, font, width);
195 lineNumber += message->lineUsed;
196 message = message->next;
197 }
198 }
199
200 /* update scroll status */
201 #ifdef AUTOSCROLL
202 if (autoscroll)
203 setScrollY(node, lineNumber, node->box.size[1] / defaultHeight, lineNumber);
204 else
205 setScrollY(node, -1, node->box.size[1] / defaultHeight, lineNumber);
206 #else
207 setScrollY(node, -1, node->box.size[1] / defaultHeight, lineNumber);
208 #endif
209
210 /* found the first message we must display */
211 message = messageStack;
212 posY = EXTRADATA(node).scrollY.viewPos;
213 while (message && posY > 0) {
214 posY -= message->lineUsed;
215 if (posY < 0)
216 break;
217 message = message->next;
218 }
219
220 /* draw */
221 /** @note posY can be negative (if we must display last line of the first message) */
222 lastDate = nullptr;
223 screenLines = posY;
224 while (message) {
225 UI_MessageDraw(node, message, font, x, y, width, &screenLines);
226 if (screenLines >= EXTRADATA(node).scrollY.viewSize)
227 break;
228 message = message->next;
229 }
230 }
231
onScroll(uiNode_t * node,int deltaX,int deltaY)232 bool uiMessageListNode::onScroll (uiNode_t* node, int deltaX, int deltaY)
233 {
234 bool down = deltaY > 0;
235 bool updated;
236 if (deltaY == 0)
237 return false;
238 updated = scrollY(node, (down ? 1 : -1));
239 /* @todo use super behaviour */
240 if (node->onWheelUp && !down) {
241 UI_ExecuteEventActions(node, node->onWheelUp);
242 updated = true;
243 }
244 if (node->onWheelDown && down) {
245 UI_ExecuteEventActions(node, node->onWheelDown);
246 updated = true;
247 }
248 if (node->onWheel) {
249 UI_ExecuteEventActions(node, node->onWheel);
250 updated = true;
251 }
252 return updated;
253 }
254
onLoading(uiNode_t * node)255 void uiMessageListNode::onLoading (uiNode_t* node)
256 {
257 Vector4Set(node->color, 1.0, 1.0, 1.0, 1.0);
258 }
259
260 /**
261 * @brief Track mouse down/up events to implement drag&drop-like scrolling, for touchscreen devices
262 * @sa UI_TextNodeMouseUp, UI_TextNodeCapturedMouseMove
263 */
onMouseDown(uiNode_t * node,int x,int y,int button)264 void uiMessageListNode::onMouseDown (uiNode_t* node, int x, int y, int button)
265 {
266 if (!UI_GetMouseCapture() && button == K_MOUSE1 &&
267 EXTRADATA(node).scrollY.fullSize > EXTRADATA(node).scrollY.viewSize) {
268 UI_SetMouseCapture(node);
269 mouseScrollX = x;
270 mouseScrollY = y;
271 }
272 }
273
onMouseUp(uiNode_t * node,int x,int y,int button)274 void uiMessageListNode::onMouseUp (uiNode_t* node, int x, int y, int button)
275 {
276 if (UI_GetMouseCapture() == node) /* More checks can never hurt */
277 UI_MouseRelease();
278 }
279
onCapturedMouseMove(uiNode_t * node,int x,int y)280 void uiMessageListNode::onCapturedMouseMove (uiNode_t* node, int x, int y)
281 {
282 const int lineHeight = getCellHeight(node);
283 const int deltaY = (mouseScrollY - y) / lineHeight;
284 /* We're doing only vertical scroll, that's enough for the most instances */
285 if (abs(mouseScrollY - y) >= lineHeight) {
286 scrollY(node, deltaY);
287 /* @todo not accurate */
288 mouseScrollX = x;
289 mouseScrollY = y;
290 }
291 onMouseMove(node, x, y);
292 }
293
294 /**
295 * @brief Return size of the cell, which is the size (in virtual "pixel") which represent 1 in the scroll values.
296 * Here we guess the widget can scroll pixel per pixel.
297 * @return Size in pixel.
298 */
getCellHeight(uiNode_t * node)299 int uiMessageListNode::getCellHeight (uiNode_t* node)
300 {
301 return LINEHEIGHT;
302 }
303
UI_RegisterMessageListNode(uiBehaviour_t * behaviour)304 void UI_RegisterMessageListNode (uiBehaviour_t* behaviour)
305 {
306 behaviour->name = "messagelist";
307 behaviour->extends = "abstractscrollable";
308 behaviour->manager = UINodePtr(new uiMessageListNode());
309 }
310