1 /* Table.cpp
2 Copyright (c) 2014-2020 by Michael Zahniser
3 
4 Endless Sky is free software: you can redistribute it and/or modify it under the
5 terms of the GNU General Public License as published by the Free Software
6 Foundation, either version 3 of the License, or (at your option) any later version.
7 
8 Endless Sky is distributed in the hope that it will be useful, but WITHOUT ANY
9 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
10 PARTICULAR PURPOSE.  See the GNU General Public License for more details.
11 */
12 
13 #include "Table.h"
14 
15 #include "DisplayText.h"
16 #include "../FillShader.h"
17 #include "Font.h"
18 #include "FontSet.h"
19 #include "Format.h"
20 #include "../Rectangle.h"
21 
22 #include <algorithm>
23 
24 using namespace std;
25 
26 
27 
Table()28 Table::Table()
29 {
30 	Clear();
31 }
32 
33 
34 
35 // Set the column positions. If no columns are set, the Table will draw a
36 // list (one column of text, left aligned).
Clear()37 void Table::Clear()
38 {
39 	columns.clear();
40 
41 	font = &FontSet::Get(14);
42 	rowSize = Point(0., 20.);
43 	center = Point(0., font->Height() / 2);
44 	lineSize = Point(0., 1.);
45 	lineOff = Point(0., font->Height() + 1);
46 
47 	point = Point();
48 	it = columns.begin();
49 	color = Color(1.f, 0.f);
50 }
51 
52 
53 
AddColumn(int x,Layout layout)54 void Table::AddColumn(int x, Layout layout)
55 {
56 	columns.emplace_back(x, layout);
57 
58 	// This may invalidate iterators, so:
59 	it = columns.begin();
60 }
61 
62 
63 
64 // Set the font size. Default is 14 pixels.
SetFontSize(int size)65 void Table::SetFontSize(int size)
66 {
67 	font = &FontSet::Get(size);
68 	lineOff.Y() = font->Height() + 1;
69 	center.Y() = font->Height() / 2;
70 }
71 
72 
73 
74 // Set the row height. Default is 20 pixels.
SetRowHeight(int height)75 void Table::SetRowHeight(int height) noexcept
76 {
77 	rowSize.Y() = height;
78 }
79 
80 
81 
82 // Set the width of the highlight area. If the underline has not been set,
83 // this will also set the width of the underline.
SetHighlight(int startX,int endX)84 void Table::SetHighlight(int startX, int endX) noexcept
85 {
86 	rowSize.X() = endX - startX;
87 	center.X() = (endX + startX) / 2;
88 
89 	if(!lineSize.X())
90 	{
91 		lineSize.X() = rowSize.X();
92 		lineOff.X() = center.X();
93 	}
94 }
95 
96 
97 
98 // Set the X range of the underline. If the highlight has not been set, this
99 // will also set the width of the highlight.
SetUnderline(int startX,int endX)100 void Table::SetUnderline(int startX, int endX) noexcept
101 {
102 	lineSize.X() = endX - startX;
103 	lineOff.X() = (endX + startX) / 2;
104 
105 	if(!rowSize.X())
106 	{
107 		rowSize.X() = lineSize.X();
108 		center.X() = lineOff.X();
109 	}
110 }
111 
112 
113 
114 // Begin drawing at the given position. Each time text is drawn, it fills a
115 // new column until all columns have been filled. Then, the Y position is
116 // increased based on the row height, and a new row begins.
DrawAt(const Point & point) const117 void Table::DrawAt(const Point &point) const
118 {
119 	this->point = point + Point(0., (rowSize.Y() - font->Height()) / 2);
120 	it = columns.begin();
121 }
122 
123 
124 
125 // Set the color for drawing text and underlines.
SetColor(const Color & color) const126 void Table::SetColor(const Color &color) const
127 {
128 	this->color = color;
129 }
130 
131 
132 
133 // Advance to the next field without drawing anything.
Advance(int fields) const134 void Table::Advance(int fields) const
135 {
136 	while(fields-- > 0)
137 	{
138 		if(columns.empty() || ++it == columns.end())
139 		{
140 			it = columns.begin();
141 			point.Y() += rowSize.Y();
142 		}
143 	}
144 }
145 
146 
147 
148 // Draw a single text field, and move on to the next one.
Draw(const char * text) const149 void Table::Draw(const char *text) const
150 {
151 	Draw(text, nullptr, color);
152 }
153 
154 
155 
Draw(const string & text) const156 void Table::Draw(const string &text) const
157 {
158 	Draw(text, nullptr, color);
159 }
160 
161 
162 
163 // If a color is given, this field is drawn using that color, but the
164 // previously set color will be used for future fields.
Draw(const char * text,const Color & color) const165 void Table::Draw(const char *text, const Color &color) const
166 {
167 	Draw(text, nullptr, color);
168 }
169 
170 
171 
Draw(const string & text,const Color & color) const172 void Table::Draw(const string &text, const Color &color) const
173 {
174 	Draw(text, nullptr, color);
175 }
176 
177 
178 
Draw(double value) const179 void Table::Draw(double value) const
180 {
181 	Draw(Format::Number(value), nullptr, color);
182 }
183 
184 
185 
Draw(double value,const Color & color) const186 void Table::Draw(double value, const Color &color) const
187 {
188 	Draw(Format::Number(value), nullptr, color);
189 }
190 
191 
192 
DrawCustom(const DisplayText & text) const193 void Table::DrawCustom(const DisplayText &text) const
194 {
195 	Draw(text.GetText(), &text.GetLayout(), color);
196 }
197 
198 
199 
DrawCustom(const DisplayText & text,const Color & color) const200 void Table::DrawCustom(const DisplayText &text, const Color &color) const
201 {
202 	Draw(text.GetText(), &text.GetLayout(), color);
203 }
204 
205 
206 
DrawTruncatedPair(const string & left,const Color & leftColor,const string & right,const Color & rightColor,Truncate strategy,bool truncateRightColumn) const207 void Table::DrawTruncatedPair(const string &left, const Color &leftColor, const string &right, const Color &rightColor,
208 	Truncate strategy, bool truncateRightColumn) const
209 {
210 	// Compute the width of the non-truncated string, and the margin we have for the possibly-large text.
211 	const auto colWidth = it->layout.width;
212 	const auto textWidth = font->FormattedWidth({truncateRightColumn ? left : right, {colWidth}});
213 	constexpr auto PAD = 5;
214 	const auto remainder = max(colWidth - PAD - textWidth, 0);
215 
216 	auto lhs = Layout(truncateRightColumn ? colWidth : remainder, Alignment::LEFT, strategy);
217 	auto rhs = Layout(truncateRightColumn ? remainder : colWidth, Alignment::RIGHT, strategy);
218 	if(truncateRightColumn)
219 		lhs.truncate = Truncate::NONE;
220 	else
221 		rhs.truncate = Truncate::NONE;
222 	Draw(left, &lhs, leftColor);
223 	Draw(right, &rhs, rightColor);
224 }
225 
226 
227 
228 // Draw an underline under the text for the current row.
DrawUnderline() const229 void Table::DrawUnderline() const
230 {
231 	DrawUnderline(color);
232 }
233 
234 
235 
DrawUnderline(const Color & color) const236 void Table::DrawUnderline(const Color &color) const
237 {
238 	FillShader::Fill(point + lineOff - Point(0., 2.), lineSize, color);
239 }
240 
241 
242 
243 // Highlight the current row.
DrawHighlight() const244 void Table::DrawHighlight() const
245 {
246 	DrawHighlight(color);
247 }
248 
249 
250 
DrawHighlight(const Color & color) const251 void Table::DrawHighlight(const Color &color) const
252 {
253 	FillShader::Fill(GetCenterPoint(), GetRowSize(), color);
254 }
255 
256 
257 
258 // Shift the draw position down by the given amount. This usually should not
259 // be called in the middle of a row, or the fields will not line up.
DrawGap(int y) const260 void Table::DrawGap(int y) const
261 {
262 	point.Y() += y;
263 }
264 
265 
266 
267 // Get the point that should be passed to DrawAt() to start the next row at
268 // the given location.
GetPoint() const269 Point Table::GetPoint() const
270 {
271 	return point - Point(0., (rowSize.Y() - font->Height()) / 2);
272 }
273 
274 
275 
276 // Get the center and size of the current row. This can be used to define
277 // what screen region constitutes a mouse click on this particular row.
GetCenterPoint() const278 Point Table::GetCenterPoint() const
279 {
280 	return point + center;
281 }
282 
283 
284 
GetRowSize() const285 Point Table::GetRowSize() const
286 {
287 	return rowSize;
288 }
289 
290 
291 
GetRowBounds() const292 Rectangle Table::GetRowBounds() const
293 {
294 	return Rectangle(GetCenterPoint(), GetRowSize());
295 }
296 
297 
298 
Column(double offset,Layout layout)299 Table::Column::Column(double offset, Layout layout) noexcept
300 	: offset(offset), layout(layout)
301 {
302 }
303 
304 
305 
Draw(const string & text,const Layout * special,const Color & color) const306 void Table::Draw(const string &text, const Layout *special, const Color &color) const
307 {
308 	if(font && !columns.empty())
309 	{
310 		const auto &layout = special ? *special : it->layout;
311 		const double alignmentOffset = layout.align == Alignment::RIGHT ? -1.
312 			: layout.align == Alignment::CENTER ? -0.5 : 0.;
313 		auto pos = point + Point(it->offset + alignmentOffset * (layout.width >= 0 ? layout.width : font->Width(text)), 0.);
314 		font->Draw({text, layout}, pos, color);
315 	}
316 
317 	Advance();
318 }
319