1 /* GG is a GUI for OpenGL.
2 Copyright (C) 2003-2008 T. Zachary Laine
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public License
6 as published by the Free Software Foundation; either version 2.1
7 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public
15 License along with this library; if not, write to the Free
16 Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
17 02111-1307 USA
18
19 If you do not wish to comply with the terms of the LGPL please
20 contact the author as other terms are available for a fee.
21
22 Zach Laine
23 whatwasthataddress@gmail.com */
24
25 #include <GG/Menu.h>
26
27 #include <GG/GUI.h>
28 #include <GG/DrawUtil.h>
29 #include <GG/StyleFactory.h>
30 #include <GG/TextControl.h>
31 #include <GG/WndEvent.h>
32
33
34 using namespace GG;
35
36 namespace {
37 const int BORDER_THICKNESS = 1; // thickness with which to draw menu borders
38 }
39
40
41 ////////////////////////////////////////////////
42 // GG::MenuItem
43 ////////////////////////////////////////////////
MenuItem()44 MenuItem::MenuItem() :
45 MenuItem("", false, false)
46 {}
47
MenuItem(bool separator_)48 MenuItem::MenuItem(bool separator_) :
49 disabled(true),
50 checked(false),
51 separator(true),
52 next_level(),
53 m_selected_on_close_callback{}
54 {}
55
MenuItem(const std::string & str,bool disable,bool check,std::function<void ()> selected_on_close_callback)56 MenuItem::MenuItem(const std::string& str, bool disable, bool check,
57 std::function<void()> selected_on_close_callback) :
58 label(str),
59 disabled(disable),
60 checked(check),
61 separator(false),
62 next_level(),
63 m_selected_on_close_callback{selected_on_close_callback}
64 {}
65
~MenuItem()66 MenuItem::~MenuItem()
67 {}
68
69
70 ////////////////////////////////////////////////
71 // GG::PopupMenu
72 ////////////////////////////////////////////////
73 namespace {
74 // distance to leave between edge of PopupMenuClassic contents and the control's border
75 const X HORIZONTAL_MARGIN(3);
76 }
77
78 const std::size_t PopupMenu::INVALID_CARET = std::numeric_limits<std::size_t>::max();
79
PopupMenu(X x,Y y,const std::shared_ptr<Font> & font,Clr text_color,Clr border_color,Clr interior_color,Clr hilite_color)80 PopupMenu::PopupMenu(X x, Y y, const std::shared_ptr<Font>& font, Clr text_color/* = CLR_WHITE*/,
81 Clr border_color/* = CLR_BLACK*/, Clr interior_color/* = CLR_SHADOW*/, Clr hilite_color/* = CLR_GRAY*/) :
82 Wnd(X0, Y0, GUI::GetGUI()->AppWidth() - 1, GUI::GetGUI()->AppHeight() - 1, INTERACTIVE | MODAL),
83 m_font(font),
84 m_border_color(border_color),
85 m_int_color(interior_color),
86 m_text_color(text_color),
87 m_hilite_color(hilite_color),
88 m_sel_text_color(text_color),
89 m_menu_data(MenuItem()),
90 m_caret(1, INVALID_CARET),
91 m_origin(x, y)
92 {
93 m_open_levels.resize(1);
94 }
95
AddMenuItem(MenuItem && menu_item)96 void PopupMenu::AddMenuItem(MenuItem&& menu_item)
97 {
98 m_menu_data.next_level.push_back(std::forward<MenuItem>(menu_item));
99 }
100
ClientUpperLeft() const101 Pt PopupMenu::ClientUpperLeft() const
102 { return m_origin; }
103
BorderColor() const104 Clr PopupMenu::BorderColor() const
105 { return m_border_color; }
106
InteriorColor() const107 Clr PopupMenu::InteriorColor() const
108 { return m_int_color; }
109
TextColor() const110 Clr PopupMenu::TextColor() const
111 { return m_text_color; }
112
HiliteColor() const113 Clr PopupMenu::HiliteColor() const
114 { return m_hilite_color; }
115
SelectedTextColor() const116 Clr PopupMenu::SelectedTextColor() const
117 { return m_sel_text_color; }
118
Render()119 void PopupMenu::Render()
120 {
121 if (m_menu_data.next_level.size())
122 {
123 Pt ul = ClientUpperLeft();
124
125 const Y INDICATOR_VERTICAL_MARGIN(3);
126 const Y INDICATOR_HEIGHT = m_font->Lineskip() - 2 * INDICATOR_VERTICAL_MARGIN;
127 const Y CHECK_HEIGHT = INDICATOR_HEIGHT;
128 const X CHECK_WIDTH(Value(CHECK_HEIGHT));
129
130 X next_menu_x_offset(0);
131 Y next_menu_y_offset(0);
132 for (std::size_t i = 0; i < m_caret.size(); ++i) {
133 bool needs_indicator = false;
134
135 // get the correct submenu
136 MenuItem* menu_ptr = &m_menu_data;
137 for (std::size_t j = 0; j < i; ++j)
138 menu_ptr = &menu_ptr->next_level[m_caret[j]];
139 MenuItem& menu = *menu_ptr;
140
141 // determine the total size of the menu, render it, and record its bounding rect
142 std::string str;
143 for (std::size_t j = 0; j < menu.next_level.size(); ++j) {
144 str += menu.next_level[j].label + (static_cast<int>(j) < static_cast<int>(menu.next_level.size()) - 1 ? "\n" : "");
145 if (menu.next_level[j].next_level.size() || menu.next_level[j].checked)
146 needs_indicator = true;
147 }
148 Flags<TextFormat> fmt = FORMAT_LEFT | FORMAT_TOP;
149 auto text_elements = m_font->ExpensiveParseFromTextToTextElements(str, fmt);
150 auto lines = m_font->DetermineLines(str, fmt, X0, text_elements);
151 Pt menu_sz = m_font->TextExtent(lines); // get dimensions of text in menu
152 menu_sz.x += 2 * HORIZONTAL_MARGIN;
153 if (needs_indicator)
154 menu_sz.x += CHECK_WIDTH + 2 * HORIZONTAL_MARGIN; // make room for the little arrow
155 Rect r(ul.x + next_menu_x_offset, ul.y + next_menu_y_offset,
156 ul.x + next_menu_x_offset + menu_sz.x, ul.y + next_menu_y_offset + menu_sz.y);
157
158 if (r.lr.x > GUI::GetGUI()->AppWidth()) {
159 X offset = r.lr.x - GUI::GetGUI()->AppWidth();
160 r.ul.x -= offset;
161 r.lr.x -= offset;
162 }
163 if (r.lr.y > GUI::GetGUI()->AppHeight()) {
164 Y offset = r.lr.y - GUI::GetGUI()->AppHeight();
165 r.ul.y -= offset;
166 r.lr.y -= offset;
167 }
168 next_menu_x_offset = menu_sz.x;
169 next_menu_y_offset = static_cast<int>(m_caret[i]) * m_font->Lineskip();
170 FlatRectangle(r.ul, r.lr, m_int_color, m_border_color, BORDER_THICKNESS);
171 m_open_levels[i] = r;
172
173 // paint caret, if any
174 if (m_caret[i] != INVALID_CARET &&
175 !menu.next_level[m_caret[i]].separator &&
176 !menu.next_level[m_caret[i]].disabled)
177 {
178 Rect tmp_r = r;
179 tmp_r.ul.y += static_cast<int>(m_caret[i]) * m_font->Lineskip();
180 tmp_r.lr.y = tmp_r.ul.y + m_font->Lineskip() + 3;
181 tmp_r.ul.x += BORDER_THICKNESS;
182 tmp_r.lr.x -= BORDER_THICKNESS;
183 if (m_caret[i] == 0)
184 tmp_r.ul.y += BORDER_THICKNESS;
185 if (m_caret[i] == menu.next_level.size() - 1)
186 tmp_r.lr.y -= BORDER_THICKNESS;
187 FlatRectangle(tmp_r.ul, tmp_r.lr, m_hilite_color, CLR_ZERO, 0);
188 }
189
190 // paint menu text and submenu indicator arrows
191 Rect line_rect = r;
192 line_rect.ul.x += HORIZONTAL_MARGIN;
193 line_rect.lr.x -= HORIZONTAL_MARGIN;
194 for (std::size_t j = 0; j < menu.next_level.size(); ++j) {
195 Clr clr = (m_caret[i] == j)
196 ? (menu.next_level[j].disabled
197 ? DisabledColor(m_sel_text_color)
198 : m_sel_text_color)
199 : (menu.next_level[j].disabled
200 ? DisabledColor(m_text_color)
201 : m_text_color);
202 glColor3ub(clr.r, clr.g, clr.b);
203
204 if (!menu.next_level[j].separator) {
205 // TODO cache line data v expensive calculation
206 auto element_data = m_font->ExpensiveParseFromTextToTextElements(menu.next_level[j].label, fmt);
207 auto line_data = m_font->DetermineLines(menu.next_level[j].label, fmt, X0, element_data);
208
209 m_font->RenderText(line_rect.ul, line_rect.lr, menu.next_level[j].label, fmt, line_data);
210
211 } else {
212 Line(line_rect.ul.x + HORIZONTAL_MARGIN, line_rect.ul.y + INDICATOR_HEIGHT/2 + INDICATOR_VERTICAL_MARGIN,
213 line_rect.lr.x - HORIZONTAL_MARGIN, line_rect.ul.y + INDICATOR_HEIGHT/2 + INDICATOR_VERTICAL_MARGIN);
214 }
215
216 if (menu.next_level[j].checked) {
217 FlatCheck(Pt(line_rect.lr.x - CHECK_WIDTH - HORIZONTAL_MARGIN, line_rect.ul.y + INDICATOR_VERTICAL_MARGIN),
218 Pt(line_rect.lr.x - HORIZONTAL_MARGIN, line_rect.ul.y + INDICATOR_VERTICAL_MARGIN + CHECK_HEIGHT),
219 clr);
220 }
221
222 // submenu indicator arrow
223 if (menu.next_level[j].next_level.size() > 0u) {
224 Triangle(line_rect.lr.x - Value(INDICATOR_HEIGHT/2) - HORIZONTAL_MARGIN,
225 line_rect.ul.y + INDICATOR_VERTICAL_MARGIN,
226 line_rect.lr.x - Value(INDICATOR_HEIGHT/2) - HORIZONTAL_MARGIN,
227 line_rect.ul.y + m_font->Lineskip() - INDICATOR_VERTICAL_MARGIN,
228 line_rect.lr.x - HORIZONTAL_MARGIN,
229 line_rect.ul.y + m_font->Lineskip()/2);
230 glEnd();
231 glEnable(GL_TEXTURE_2D);
232 }
233 line_rect.ul.y += m_font->Lineskip();
234 }
235 }
236 }
237 }
238
LButtonUp(const Pt & pt,Flags<ModKey> mod_keys)239 void PopupMenu::LButtonUp(const Pt& pt, Flags<ModKey> mod_keys)
240 {
241 if (m_caret[0] != INVALID_CARET) {
242 MenuItem* menu_ptr = &m_menu_data;
243 for (std::size_t caret : m_caret) {
244 if (caret != INVALID_CARET) {
245 menu_ptr = &menu_ptr->next_level[caret];
246 }
247 }
248 if (!menu_ptr->disabled && !menu_ptr->separator) {
249 m_item_selected = menu_ptr;
250 m_done = true;
251 }
252 } else {
253 m_done = true;
254 }
255 }
256
LClick(const Pt & pt,Flags<ModKey> mod_keys)257 void PopupMenu::LClick(const Pt& pt, Flags<ModKey> mod_keys)
258 { LButtonUp(pt, mod_keys); }
259
LDrag(const Pt & pt,const Pt & move,Flags<ModKey> mod_keys)260 void PopupMenu::LDrag(const Pt& pt, const Pt& move, Flags<ModKey> mod_keys)
261 {
262 bool cursor_is_in_menu = false;
263 for (int i = static_cast<int>(m_open_levels.size()) - 1; i >= 0; --i) {
264 // get the correct submenu
265 MenuItem* menu_ptr = &m_menu_data;
266 for (int j = 0; j < i; ++j)
267 menu_ptr = &menu_ptr->next_level[m_caret[j]];
268 MenuItem& menu = *menu_ptr;
269
270 if (pt.x >= m_open_levels[i].ul.x && pt.x <= m_open_levels[i].lr.x &&
271 pt.y >= m_open_levels[i].ul.y && pt.y <= m_open_levels[i].lr.y)
272 {
273 std::size_t row_selected = Value((pt.y - m_open_levels[i].ul.y) / m_font->Lineskip());
274 if (row_selected == m_caret[i]) {
275 cursor_is_in_menu = true;
276 } else if (row_selected < menu.next_level.size()) {
277 m_caret[i] = row_selected;
278 m_open_levels.resize(i + 1);
279 m_caret.resize(i + 1);
280 if (!menu.next_level[row_selected].disabled && menu.next_level[row_selected].next_level.size()) {
281 m_caret.push_back(INVALID_CARET);
282 m_open_levels.push_back(Rect());
283 }
284 cursor_is_in_menu = true;
285 }
286 }
287 }
288 if (!cursor_is_in_menu) {
289 m_open_levels.resize(1);
290 m_caret.resize(1);
291 m_caret[0] = INVALID_CARET;
292 }
293 }
294
RButtonUp(const Pt & pt,Flags<ModKey> mod_keys)295 void PopupMenu::RButtonUp(const Pt& pt, Flags<ModKey> mod_keys)
296 { LButtonUp(pt, mod_keys); }
297
RClick(const Pt & pt,Flags<ModKey> mod_keys)298 void PopupMenu::RClick(const Pt& pt, Flags<ModKey> mod_keys)
299 { LButtonUp(pt, mod_keys); }
300
MouseHere(const Pt & pt,Flags<ModKey> mod_keys)301 void PopupMenu::MouseHere(const Pt& pt, Flags<ModKey> mod_keys)
302 { LDrag(pt, Pt(), mod_keys); }
303
Run()304 bool PopupMenu::Run()
305 {
306 bool retval = Wnd::Run();
307 if (retval
308 && m_item_selected
309 && m_item_selected->m_selected_on_close_callback)
310 {
311 m_item_selected->m_selected_on_close_callback();
312 }
313
314 return retval;
315 }
316
SetBorderColor(Clr clr)317 void PopupMenu::SetBorderColor(Clr clr)
318 { m_border_color = clr; }
319
SetInteriorColor(Clr clr)320 void PopupMenu::SetInteriorColor(Clr clr)
321 { m_int_color = clr; }
322
SetTextColor(Clr clr)323 void PopupMenu::SetTextColor(Clr clr)
324 { m_text_color = clr; }
325
SetHiliteColor(Clr clr)326 void PopupMenu::SetHiliteColor(Clr clr)
327 { m_hilite_color = clr; }
328
SetSelectedTextColor(Clr clr)329 void PopupMenu::SetSelectedTextColor(Clr clr)
330 { m_sel_text_color = clr; }
331
GetFont() const332 const std::shared_ptr<Font>& PopupMenu::GetFont() const
333 { return m_font; }
334
MenuData() const335 const MenuItem& PopupMenu::MenuData() const
336 { return m_menu_data; }
337
OpenLevels() const338 const std::vector<Rect>& PopupMenu::OpenLevels() const
339 { return m_open_levels; }
340
Caret() const341 const std::vector<std::size_t>& PopupMenu::Caret() const
342 { return m_caret; }
343
ItemSelected() const344 const MenuItem* PopupMenu::ItemSelected() const
345 { return m_item_selected; }
346