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/Button.h>
26
27 #include <GG/DrawUtil.h>
28 #include <GG/Layout.h>
29 #include <GG/StyleFactory.h>
30 #include <GG/WndEvent.h>
31
32
33 using namespace GG;
34
35 namespace {
ClickedEcho()36 void ClickedEcho()
37 { std::cerr << "GG SIGNAL : Button::LeftClickedSignal()" << std::endl; }
38
CheckedEcho(bool checked)39 void CheckedEcho(bool checked)
40 { std::cerr << "GG SIGNAL : StateButton::CheckedSignal(checked=" << checked << ")" << std::endl; }
41
ButtonChangedEcho(std::size_t index)42 void ButtonChangedEcho(std::size_t index)
43 {
44 std::cerr << "GG SIGNAL : RadioButtonGroup::ButtonChangedSignal(index="
45 << index << ")" << std::endl;
46 }
47 }
48
49 ////////////////////////////////////////////////
50 // GG::Button
51 ////////////////////////////////////////////////
Button(const std::string & str,const std::shared_ptr<Font> & font,Clr color,Clr text_color,Flags<WndFlag> flags)52 Button::Button(const std::string& str, const std::shared_ptr<Font>& font, Clr color,
53 Clr text_color/* = CLR_BLACK*/, Flags<WndFlag> flags/* = INTERACTIVE*/) :
54 Control(X0, Y0, X1, Y1, flags),
55 m_label(Wnd::Create<TextControl>(X0, Y0, X1, Y1, str, font, text_color, FORMAT_NONE, NO_WND_FLAGS)),
56 m_state(BN_UNPRESSED)
57 {
58 m_color = color;
59 m_label->Hide();
60
61 if (INSTRUMENT_ALL_SIGNALS)
62 LeftClickedSignal.connect(&ClickedEcho);
63 }
64
CompleteConstruction()65 void Button::CompleteConstruction()
66 { AttachChild(m_label); }
67
MinUsableSize() const68 Pt Button::MinUsableSize() const
69 { return m_label->MinUsableSize(); }
70
Show()71 void Button::Show()
72 {
73 Wnd::Show();
74 m_label->Hide();
75 }
76
State() const77 Button::ButtonState Button::State() const
78 { return m_state; }
79
Text() const80 const std::string& Button::Text() const
81 { return m_label->Text(); }
82
UnpressedGraphic() const83 const SubTexture& Button::UnpressedGraphic() const
84 { return m_unpressed_graphic; }
85
PressedGraphic() const86 const SubTexture& Button::PressedGraphic() const
87 { return m_pressed_graphic; }
88
RolloverGraphic() const89 const SubTexture& Button::RolloverGraphic() const
90 { return m_rollover_graphic; }
91
Render()92 void Button::Render()
93 {
94 switch (m_state) {
95 case BN_PRESSED: RenderPressed(); break;
96 case BN_UNPRESSED: RenderUnpressed(); break;
97 case BN_ROLLOVER: RenderRollover(); break;
98 }
99 }
100
SizeMove(const Pt & ul,const Pt & lr)101 void Button::SizeMove(const Pt& ul, const Pt& lr)
102 {
103 Wnd::SizeMove(ul, lr);
104 m_label->Resize(Size());
105 }
106
SetColor(Clr c)107 void Button::SetColor(Clr c)
108 { Control::SetColor(c); }
109
SetState(ButtonState state)110 void Button::SetState(ButtonState state)
111 { m_state = state; }
112
SetText(const std::string & text)113 void Button::SetText(const std::string& text)
114 { m_label->SetText(text); }
115
SetUnpressedGraphic(const SubTexture & st)116 void Button::SetUnpressedGraphic(const SubTexture& st)
117 { m_unpressed_graphic = st; }
118
SetPressedGraphic(const SubTexture & st)119 void Button::SetPressedGraphic(const SubTexture& st)
120 { m_pressed_graphic = st; }
121
SetRolloverGraphic(const SubTexture & st)122 void Button::SetRolloverGraphic(const SubTexture& st)
123 { m_rollover_graphic = st; }
124
LButtonDown(const Pt & pt,Flags<ModKey> mod_keys)125 void Button::LButtonDown(const Pt& pt, Flags<ModKey> mod_keys)
126 {
127 if (Disabled())
128 return;
129
130 ButtonState prev_state = m_state;
131 m_state = BN_PRESSED;
132 if (prev_state == BN_PRESSED && RepeatButtonDown())
133 LeftClickedSignal();
134 else if (prev_state != BN_PRESSED)
135 LeftPressedSignal();
136 }
137
LDrag(const Pt & pt,const Pt & move,Flags<ModKey> mod_keys)138 void Button::LDrag(const Pt& pt, const Pt& move, Flags<ModKey> mod_keys)
139 {
140 if (!Disabled())
141 m_state = BN_PRESSED;
142 Wnd::LDrag(pt, move, mod_keys);
143 }
144
LButtonUp(const Pt & pt,Flags<ModKey> mod_keys)145 void Button::LButtonUp(const Pt& pt, Flags<ModKey> mod_keys)
146 {
147 if (!Disabled())
148 m_state = BN_UNPRESSED;
149 }
150
LClick(const Pt & pt,Flags<ModKey> mod_keys)151 void Button::LClick(const Pt& pt, Flags<ModKey> mod_keys)
152 {
153 if (!Disabled()) {
154 m_state = BN_ROLLOVER;
155 LeftClickedSignal();
156 }
157 }
158
RButtonDown(const Pt & pt,Flags<ModKey> mod_keys)159 void Button::RButtonDown(const Pt& pt, Flags<ModKey> mod_keys)
160 {
161 if (Disabled())
162 return;
163
164 ButtonState prev_state = m_state;
165 m_state = BN_PRESSED;
166 if (prev_state == BN_PRESSED && RepeatButtonDown())
167 RightClickedSignal();
168 else if (prev_state != BN_PRESSED)
169 RightPressedSignal();
170 }
171
RDrag(const Pt & pt,const Pt & move,Flags<ModKey> mod_keys)172 void Button::RDrag(const Pt& pt, const Pt& move, Flags<ModKey> mod_keys)
173 {
174 if (!Disabled())
175 m_state = BN_PRESSED;
176 Wnd::LDrag(pt, move, mod_keys);
177 }
178
RButtonUp(const Pt & pt,Flags<ModKey> mod_keys)179 void Button::RButtonUp(const Pt& pt, Flags<ModKey> mod_keys)
180 {
181 if (!Disabled())
182 m_state = BN_UNPRESSED;
183 }
184
RClick(const Pt & pt,Flags<ModKey> mod_keys)185 void Button::RClick(const Pt& pt, Flags<ModKey> mod_keys)
186 {
187 if (!Disabled()) {
188 m_state = BN_ROLLOVER;
189 RightClickedSignal();
190 }
191 }
192
MouseEnter(const Pt & pt,Flags<ModKey> mod_keys)193 void Button::MouseEnter(const Pt& pt, Flags<ModKey> mod_keys)
194 {
195 if (!Disabled())
196 m_state = BN_ROLLOVER;
197 }
198
MouseHere(const Pt & pt,Flags<ModKey> mod_keys)199 void Button::MouseHere(const Pt& pt, Flags<ModKey> mod_keys)
200 {
201 if (!Disabled())
202 m_state = BN_ROLLOVER;
203 }
204
MouseLeave()205 void Button::MouseLeave()
206 {
207 if (!Disabled())
208 m_state = BN_UNPRESSED;
209 }
210
RenderUnpressed()211 void Button::RenderUnpressed()
212 {
213 if (!m_unpressed_graphic.Empty()) {
214 glColor(Disabled() ? DisabledColor(m_color) : m_color);
215 m_unpressed_graphic.OrthoBlit(UpperLeft(), LowerRight());
216 } else {
217 RenderDefault();
218 }
219 // draw text shadow
220 Clr temp = m_label->TextColor(); // save original color
221 m_label->SetTextColor(CLR_SHADOW); // shadow color
222 m_label->OffsetMove(Pt(X(2), Y(2)));
223 m_label->Render();
224 m_label->OffsetMove(Pt(X(-2), Y(-2)));
225 m_label->SetTextColor(temp); // restore original color
226 // draw text
227 m_label->Render();
228 }
229
RenderPressed()230 void Button::RenderPressed()
231 {
232 if (!m_pressed_graphic.Empty()) {
233 glColor(Disabled() ? DisabledColor(m_color) : m_color);
234 m_pressed_graphic.OrthoBlit(UpperLeft(), LowerRight());
235 } else {
236 RenderDefault();
237 }
238 m_label->OffsetMove(Pt(X1, Y1));
239 m_label->Render();
240 m_label->OffsetMove(Pt(-X1, -Y1));
241 }
242
RenderRollover()243 void Button::RenderRollover()
244 {
245 if (!m_rollover_graphic.Empty()) {
246 glColor(Disabled() ? DisabledColor(m_color) : m_color);
247 m_rollover_graphic.OrthoBlit(UpperLeft(), LowerRight());
248 } else {
249 RenderDefault();
250 }
251 // draw text shadow
252 Clr temp = m_label->TextColor(); // save original color
253 m_label->SetTextColor(CLR_SHADOW); // shadow color
254 m_label->OffsetMove(Pt(X(2), Y(2)));
255 m_label->Render();
256 m_label->OffsetMove(Pt(X(-2), Y(-2)));
257 m_label->SetTextColor(temp); // restore original color
258 // draw text
259 m_label->Render();
260 }
261
RenderDefault()262 void Button::RenderDefault()
263 {
264 Pt ul = UpperLeft(), lr = LowerRight();
265 BeveledRectangle(ul, lr,
266 Disabled() ? DisabledColor(m_color) : m_color,
267 Disabled() ? DisabledColor(m_color) : m_color,
268 (m_state != BN_PRESSED), 1);
269 }
270
271
272 ////////////////////////////////////////////////
273 // GG::StateButton
274 ////////////////////////////////////////////////
StateButton(const std::string & str,const std::shared_ptr<Font> & font,Flags<TextFormat> format,Clr color,std::shared_ptr<StateButtonRepresenter> representer,Clr text_color)275 StateButton::StateButton(const std::string& str, const std::shared_ptr<Font>& font,
276 Flags<TextFormat> format, Clr color,
277 std::shared_ptr<StateButtonRepresenter> representer,
278 Clr text_color/* = CLR_BLACK*/) :
279 Control(X0, Y0, X1, Y1, INTERACTIVE),
280 m_representer(representer),
281 m_label(Wnd::Create<TextControl>(X0, Y0, X1, Y1, str, font, text_color, format, NO_WND_FLAGS)),
282 m_state(BN_UNPRESSED),
283 m_checked(false)
284 {
285 m_color = color;
286 }
287
CompleteConstruction()288 void StateButton::CompleteConstruction()
289 {
290 AttachChild(m_label);
291 m_label->Hide();
292
293 if (INSTRUMENT_ALL_SIGNALS)
294 CheckedSignal.connect(&CheckedEcho);
295 }
296
MinUsableSize() const297 Pt StateButton::MinUsableSize() const
298 {
299 if (m_representer)
300 return m_representer->MinUsableSize(*this);
301
302 return Pt();
303 }
304
State() const305 StateButton::ButtonState StateButton::State() const
306 { return m_state; }
307
Text() const308 const std::string& StateButton::Text() const
309 { return m_label->Text(); }
310
Checked() const311 bool StateButton::Checked() const
312 { return m_checked; }
313
Render()314 void StateButton::Render()
315 {
316 if (m_representer)
317 m_representer->Render(*this);
318 }
319
Show()320 void StateButton::Show()
321 {
322 Wnd::Show();
323 m_label->Hide();
324 }
325
LButtonDown(const Pt & pt,Flags<ModKey> mod_keys)326 void StateButton::LButtonDown(const Pt& pt, Flags<ModKey> mod_keys)
327 { SetState(BN_PRESSED); }
328
LDrag(const Pt & pt,const Pt & move,Flags<ModKey> mod_keys)329 void StateButton::LDrag(const Pt& pt, const Pt& move, Flags<ModKey> mod_keys)
330 {
331 SetState(BN_PRESSED);
332 Wnd::LDrag(pt, move, mod_keys);
333 }
334
LButtonUp(const Pt & pt,Flags<ModKey> mod_keys)335 void StateButton::LButtonUp(const Pt& pt, Flags<ModKey> mod_keys)
336 { SetState(BN_UNPRESSED); }
337
LClick(const Pt & pt,Flags<ModKey> mod_keys)338 void StateButton::LClick(const Pt& pt, Flags<ModKey> mod_keys)
339 {
340 if (!Disabled()) {
341 SetCheck(!m_checked);
342 if (m_representer)
343 m_representer->OnChecked(m_checked);
344 CheckedSignal(m_checked);
345 }
346 }
347
MouseHere(const GG::Pt & pt,GG::Flags<GG::ModKey> mod_keys)348 void StateButton::MouseHere(const GG::Pt& pt, GG::Flags<GG::ModKey> mod_keys)
349 { SetState(BN_ROLLOVER); }
350
MouseLeave()351 void StateButton::MouseLeave()
352 { SetState(BN_UNPRESSED); }
353
SetState(ButtonState next_state)354 void StateButton::SetState(ButtonState next_state) {
355 if (!Disabled() && next_state != m_state) {
356 ButtonState prev_state = m_state;
357 m_state = next_state;
358 if (m_representer)
359 m_representer->OnChanged(*this, prev_state);
360 }
361 }
362
SizeMove(const Pt & ul,const Pt & lr)363 void StateButton::SizeMove(const Pt& ul, const Pt& lr)
364 {
365 Control::SizeMove(ul, lr);
366 m_label->Resize(Size());
367 }
368
Reset()369 void StateButton::Reset()
370 { SetCheck(false); }
371
SetCheck(bool b)372 void StateButton::SetCheck(bool b/* = true*/)
373 { m_checked = b; }
374
SetTextColor(Clr c)375 void StateButton::SetTextColor(Clr c)
376 { m_label->SetTextColor(c); }
377
GetLabel() const378 TextControl* StateButton::GetLabel() const
379 { return m_label.get(); }
380
381
382 ////////////////////////////////////////////////
383 // GG::StateButtonRepresenter
384 ////////////////////////////////////////////////
Render(const GG::StateButton & button) const385 void StateButtonRepresenter::Render(const GG::StateButton& button) const
386 {}
387
DoLayout(const GG::StateButton & button,Pt & button_ul,Pt & button_lr,Pt & text_ul) const388 void StateButtonRepresenter::DoLayout(const GG::StateButton& button, Pt& button_ul, Pt& button_lr, Pt& text_ul) const
389 {
390 X bn_w = X(button.GetLabel()->GetFont()->PointSize()); // set button width and height to text he
391 Y bn_h = Y(button.GetLabel()->GetFont()->PointSize());
392
393 button_ul = Pt();
394 button_lr = Pt(bn_w, bn_h);
395
396 X w = button.Width();
397 Y h = button.Height();
398 const X BN_W = button_lr.x - button_ul.x;
399 const Y BN_H = button_lr.y - button_ul.y;
400 X bn_x = button_ul.x;
401 Y bn_y = button_ul.y;
402 Flags<TextFormat> format = button.GetLabel()->GetTextFormat();
403 Flags<TextFormat> original_format = format;
404 const double SPACING = 0.5; // the space to leave between the button and text, as a factor of the button's size (width or height)
405 if (format & FORMAT_VCENTER) // center button vertically
406 bn_y = (h - BN_H) / 2.0 + 0.5;
407 if (format & FORMAT_TOP) { // put button at top, text just below
408 bn_y = Y0;
409 text_ul.y = BN_H;
410 }
411 if (format & FORMAT_BOTTOM) { // put button at bottom, text just above
412 bn_y = (h - BN_H);
413 text_ul.y = h - (BN_H * (1 + SPACING)) - (std::max(0, static_cast<int>(button.GetLabel()->GetLineData().size() - 1)) * button.GetLabel()->GetFont()->Lineskip() + button.GetLabel()->GetFont()->Height()) + 0.5;
414 }
415
416 if (format & FORMAT_CENTER) { // center button horizontally
417 if (format & FORMAT_VCENTER) { // if both the button and the text are to be centered, bad things happen
418 format |= FORMAT_LEFT; // so go to the default (FORMAT_CENTER|FORMAT_LEFT)
419 format &= ~FORMAT_CENTER;
420 } else {
421 bn_x = (w - bn_x) / 2.0 - BN_W / 2.0 + 0.5;
422 }
423 }
424 if (format & FORMAT_LEFT) { // put button at left, text just to the right
425 bn_x = X0;
426 if (format & FORMAT_VCENTER)
427 text_ul.x = BN_W * (1 + SPACING) + 0.5;
428 }
429 if (format & FORMAT_RIGHT) { // put button at right, text just to the left
430 bn_x = (w - BN_W);
431 if (format & FORMAT_VCENTER)
432 text_ul.x = -BN_W * (1 + SPACING) + 0.5;
433 }
434 if (format != original_format)
435 button.GetLabel()->SetTextFormat(format);
436 button_ul = Pt(bn_x, bn_y);
437 button_lr = button_ul + Pt(BN_W, BN_H);
438 }
439
OnChanged(const StateButton & button,StateButton::ButtonState previous_state) const440 void StateButtonRepresenter::OnChanged(const StateButton& button, StateButton::ButtonState previous_state) const
441 {}
442
OnChecked(bool checked) const443 void StateButtonRepresenter::OnChecked(bool checked) const
444 {}
445
MinUsableSize(const StateButton & button) const446 Pt StateButtonRepresenter::MinUsableSize(const StateButton& button) const
447 {
448 Pt bn_ul, bn_lr, tx_ul;
449
450 DoLayout(button, bn_ul, bn_lr, tx_ul);
451
452 Pt text_lr = tx_ul + button.GetLabel()->MinUsableSize();
453 return Pt(std::max(bn_lr.x, text_lr.x) - std::min(bn_ul.x, tx_ul.x),
454 std::max(bn_lr.y, text_lr.y) - std::min(bn_ul.y, tx_ul.y));
455 }
456
457
458 ////////////////////////////////////////////////
459 // GG::BeveledCheckBoxRepresenter
460 ////////////////////////////////////////////////
BeveledCheckBoxRepresenter(Clr interior)461 BeveledCheckBoxRepresenter::BeveledCheckBoxRepresenter(Clr interior/* = CLR_ZERO*/):
462 m_int_color(interior)
463 {}
464
Render(const GG::StateButton & button) const465 void BeveledCheckBoxRepresenter::Render(const GG::StateButton& button) const
466 {
467 const int BEVEL = 2;
468
469 // draw button
470 Pt cl_ul = button.ClientUpperLeft();
471 Pt bn_ul, bn_lr, tx_ul;
472
473 DoLayout(button, bn_ul, bn_lr, tx_ul);
474
475 bn_ul += cl_ul;
476 bn_lr += cl_ul;
477
478 const Pt DOUBLE_BEVEL(X(2 * BEVEL), Y(2 * BEVEL));
479
480 BeveledRectangle(bn_ul, bn_lr,
481 button.Disabled() ? DisabledColor(m_int_color) : m_int_color,
482 button.Disabled() ? DisabledColor(button.Color()) : button.Color(),
483 false, BEVEL);
484 if (button.Checked())
485 BeveledCheck(bn_ul + DOUBLE_BEVEL, bn_lr - DOUBLE_BEVEL,
486 button.Disabled() ? DisabledColor(button.Color()) : button.Color());
487
488 button.GetLabel()->OffsetMove(tx_ul);
489 button.GetLabel()->Render();
490 button.GetLabel()->OffsetMove(-tx_ul);
491 }
492
493
494 ////////////////////////////////////////////////
495 // GG::BeveledRadioRepresenter
496 ////////////////////////////////////////////////
BeveledRadioRepresenter(Clr interior)497 BeveledRadioRepresenter::BeveledRadioRepresenter(Clr interior/* = CLR_ZERO*/):
498 m_int_color(interior)
499 {}
500
Render(const GG::StateButton & button) const501 void BeveledRadioRepresenter::Render(const GG::StateButton& button) const
502 {
503 const int BEVEL = 2;
504
505 // draw button
506 Pt cl_ul = button.ClientUpperLeft();
507 Pt bn_ul, bn_lr, tx_ul;
508
509 DoLayout(button, bn_ul, bn_lr, tx_ul);
510
511 bn_ul += cl_ul;
512 bn_lr += cl_ul;
513
514 const Pt DOUBLE_BEVEL(X(2 * BEVEL), Y(2 * BEVEL));
515
516 BeveledCircle(bn_ul, bn_lr,
517 button.Disabled() ? DisabledColor(m_int_color) : m_int_color,
518 button.Disabled() ? DisabledColor(button.Color()) : button.Color(),
519 false, BEVEL);
520 if (button.Checked())
521 Bubble(bn_ul + DOUBLE_BEVEL, bn_lr - DOUBLE_BEVEL,
522 button.Disabled() ? DisabledColor(button.Color()) : button.Color());
523
524 button.GetLabel()->OffsetMove(tx_ul);
525 button.GetLabel()->Render();
526 button.GetLabel()->OffsetMove(-(tx_ul));
527 }
528
MinUsableSize(const StateButton & button) const529 Pt BeveledTabRepresenter::MinUsableSize(const StateButton& button) const
530 { return button.GetLabel()->MinUsableSize(); }
531
532
533 ////////////////////////////////////////////////
534 // GG::BeveledTabRepresenter
535 ////////////////////////////////////////////////
Render(const StateButton & button) const536 void BeveledTabRepresenter::Render(const StateButton& button) const
537 {
538 const int BEVEL = 2;
539
540 // draw button
541 Pt cl_ul = button.ClientUpperLeft();
542 Pt cl_lr = button.ClientLowerRight();
543 Pt tx_ul = Pt();
544
545 Clr color_to_use = button.Checked() ? button.Color() : DarkenClr(button.Color());
546 color_to_use = button.Disabled() ? DisabledColor(color_to_use) : color_to_use;
547 if (!button.Checked()) {
548 cl_ul.y += BEVEL;
549 tx_ul.y = Y(BEVEL / 2);
550 }
551 BeveledRectangle(cl_ul, cl_lr,
552 color_to_use, color_to_use,
553 true, BEVEL,
554 true, true, true, !button.Checked());
555
556 button.GetLabel()->OffsetMove(tx_ul);
557 button.GetLabel()->Render();
558 button.GetLabel()->OffsetMove(-(tx_ul));
559 }
560
561
562 ////////////////////////////////////////////////
563 // GG::RadioButtonGroup
564 ////////////////////////////////////////////////
565 // ButtonSlot
ButtonSlot(std::shared_ptr<StateButton> & button_)566 RadioButtonGroup::ButtonSlot::ButtonSlot(std::shared_ptr<StateButton>& button_) :
567 button(button_)
568 {}
569
570 // RadioButtonGroup
571 // static(s)
572 const std::size_t RadioButtonGroup::NO_BUTTON = std::numeric_limits<std::size_t>::max();
573
RadioButtonGroup(Orientation orientation)574 RadioButtonGroup::RadioButtonGroup(Orientation orientation) :
575 Control(X0, Y0, X1, Y1),
576 m_orientation(orientation),
577 m_checked_button(NO_BUTTON),
578 m_expand_buttons(false),
579 m_expand_buttons_proportionally(false),
580 m_render_outline(false)
581 {
582 SetColor(CLR_YELLOW);
583
584 if (INSTRUMENT_ALL_SIGNALS)
585 ButtonChangedSignal.connect(&ButtonChangedEcho);
586 }
587
MinUsableSize() const588 Pt RadioButtonGroup::MinUsableSize() const
589 {
590 Pt retval;
591 for (const ButtonSlot& button_slot : m_button_slots) {
592 Pt min_usable_size = button_slot.button->MinUsableSize();
593 if (m_orientation == VERTICAL) {
594 retval.x = std::max(retval.x, min_usable_size.x);
595 retval.y += min_usable_size.y;
596 } else {
597 retval.x += min_usable_size.x;
598 retval.y = std::max(retval.y, min_usable_size.y);
599 }
600 }
601 return retval;
602 }
603
GetOrientation() const604 Orientation RadioButtonGroup::GetOrientation() const
605 { return m_orientation; }
606
Empty() const607 bool RadioButtonGroup::Empty() const
608 { return m_button_slots.empty(); }
609
NumButtons() const610 std::size_t RadioButtonGroup::NumButtons() const
611 { return m_button_slots.size(); }
612
CheckedButton() const613 std::size_t RadioButtonGroup::CheckedButton() const
614 { return m_checked_button; }
615
ExpandButtons() const616 bool RadioButtonGroup::ExpandButtons() const
617 { return m_expand_buttons; }
618
ExpandButtonsProportionally() const619 bool RadioButtonGroup::ExpandButtonsProportionally() const
620 { return m_expand_buttons_proportionally; }
621
RenderOutline() const622 bool RadioButtonGroup::RenderOutline() const
623 { return m_render_outline; }
624
RaiseCheckedButton()625 void RadioButtonGroup::RaiseCheckedButton()
626 {
627 if (m_checked_button != NO_BUTTON)
628 MoveChildUp(m_button_slots[m_checked_button].button.get());
629 }
630
Render()631 void RadioButtonGroup::Render()
632 {
633 if (m_render_outline) {
634 Pt ul = UpperLeft(), lr = LowerRight();
635 Clr color_to_use = Disabled() ? DisabledColor(Color()) : Color();
636 FlatRectangle(ul, lr, CLR_ZERO, color_to_use, 1);
637 }
638 }
639
SetCheck(std::size_t index)640 void RadioButtonGroup::SetCheck(std::size_t index)
641 {
642 if (m_button_slots.size() <= index)
643 index = NO_BUTTON;
644 SetCheckImpl(index, false);
645 }
646
DisableButton(std::size_t index,bool b)647 void RadioButtonGroup::DisableButton(std::size_t index, bool b/* = true*/)
648 {
649 if (index < m_button_slots.size()) {
650 bool was_disabled = m_button_slots[index].button->Disabled();
651 m_button_slots[index].button->Disable(b);
652 if (b && !was_disabled && index == m_checked_button)
653 SetCheck(NO_BUTTON);
654 }
655 }
656
AddButton(std::shared_ptr<StateButton> bn)657 void RadioButtonGroup::AddButton(std::shared_ptr<StateButton> bn)
658 { InsertButton(m_button_slots.size(), std::forward<std::shared_ptr<StateButton>>(bn)); }
659
InsertButton(std::size_t index,std::shared_ptr<StateButton> bn)660 void RadioButtonGroup::InsertButton(std::size_t index, std::shared_ptr<StateButton> bn)
661 {
662 assert(index <= m_button_slots.size());
663 if (!m_expand_buttons) {
664 Pt min_usable_size = bn->MinUsableSize();
665 bn->Resize(Pt(std::max(bn->Width(), min_usable_size.x), std::max(bn->Height(), min_usable_size.y)));
666 }
667 Pt bn_sz = bn->Size();
668 auto&& layout = GetLayout();
669 if (!layout) {
670 layout = Wnd::Create<Layout>(X0, Y0, ClientWidth(), ClientHeight(), 1, 1);
671 SetLayout(layout);
672 }
673 const int CELLS_PER_BUTTON = m_expand_buttons ? 1 : 2;
674 const int X_STRETCH = (m_expand_buttons && m_expand_buttons_proportionally) ? Value(bn_sz.x) : 1;
675 const int Y_STRETCH = (m_expand_buttons && m_expand_buttons_proportionally) ? Value(bn_sz.y) : 1;
676 if (m_button_slots.empty()) {
677 layout->Add(bn, 0, 0);
678 if (m_expand_buttons) {
679 if (m_orientation == VERTICAL)
680 layout->SetRowStretch(0, Y_STRETCH);
681 else
682 layout->SetColumnStretch(0, X_STRETCH);
683 }
684 } else {
685 if (m_orientation == VERTICAL) {
686 layout->ResizeLayout(layout->Rows() + CELLS_PER_BUTTON, 1);
687 layout->SetRowStretch(layout->Rows() - CELLS_PER_BUTTON, Y_STRETCH);
688 } else {
689 layout->ResizeLayout(1, layout->Columns() + CELLS_PER_BUTTON);
690 layout->SetColumnStretch(layout->Columns() - CELLS_PER_BUTTON, X_STRETCH);
691 }
692 for (std::size_t i = m_button_slots.size() - 1; index <= i; --i) {
693 layout->Remove(m_button_slots[i].button.get());
694 layout->Add(m_button_slots[i].button,
695 m_orientation == VERTICAL ? i * CELLS_PER_BUTTON + CELLS_PER_BUTTON : 0,
696 m_orientation == VERTICAL ? 0 : i * CELLS_PER_BUTTON + CELLS_PER_BUTTON);
697 if (m_orientation == VERTICAL)
698 layout->SetMinimumRowHeight(i * CELLS_PER_BUTTON + CELLS_PER_BUTTON, layout->MinimumRowHeight(i * CELLS_PER_BUTTON));
699 else
700 layout->SetMinimumColumnWidth(i * CELLS_PER_BUTTON + CELLS_PER_BUTTON, layout->MinimumColumnWidth(i * CELLS_PER_BUTTON));
701 }
702 layout->Add(bn, m_orientation == VERTICAL ? index * CELLS_PER_BUTTON : 0, m_orientation == VERTICAL ? 0 : index * CELLS_PER_BUTTON);
703 }
704 if (m_orientation == VERTICAL)
705 layout->SetMinimumRowHeight(index * CELLS_PER_BUTTON, bn_sz.y);
706 else
707 layout->SetMinimumColumnWidth(index * CELLS_PER_BUTTON, bn_sz.x);
708 m_button_slots.insert(m_button_slots.begin() + index, ButtonSlot(bn));
709
710 if (m_checked_button != NO_BUTTON && index <= m_checked_button)
711 ++m_checked_button;
712 Reconnect();
713 }
714
RemoveButton(StateButton * button)715 void RadioButtonGroup::RemoveButton(StateButton* button)
716 {
717 std::size_t index = NO_BUTTON;
718 for (std::size_t i = 0; i < m_button_slots.size(); ++i) {
719 if (m_button_slots[i].button.get() == button) {
720 index = i;
721 break;
722 }
723 }
724 assert(index < m_button_slots.size());
725
726 const int CELLS_PER_BUTTON = m_expand_buttons ? 1 : 2;
727 auto&& layout = GetLayout();
728 layout->Remove(m_button_slots[index].button.get());
729 for (std::size_t i = index + 1; i < m_button_slots.size(); ++i) {
730 layout->Remove(m_button_slots[i].button.get());
731 if (m_orientation == VERTICAL) {
732 layout->Add(m_button_slots[i].button, i * CELLS_PER_BUTTON - CELLS_PER_BUTTON, 0);
733 layout->SetRowStretch(i * CELLS_PER_BUTTON - CELLS_PER_BUTTON, layout->RowStretch(i * CELLS_PER_BUTTON));
734 layout->SetMinimumRowHeight(i * CELLS_PER_BUTTON - CELLS_PER_BUTTON, layout->MinimumRowHeight(i * CELLS_PER_BUTTON));
735 } else {
736 layout->Add(m_button_slots[i].button, 0, i * CELLS_PER_BUTTON - CELLS_PER_BUTTON);
737 layout->SetColumnStretch(i * CELLS_PER_BUTTON - CELLS_PER_BUTTON, layout->ColumnStretch(i * CELLS_PER_BUTTON));
738 layout->SetMinimumColumnWidth(i * CELLS_PER_BUTTON - CELLS_PER_BUTTON, layout->MinimumColumnWidth(i * CELLS_PER_BUTTON));
739 }
740 }
741 m_button_slots[index].connection.disconnect();
742 m_button_slots.erase(m_button_slots.begin() + index);
743 if (m_button_slots.empty()) {
744 layout->ResizeLayout(1, 1);
745 } else {
746 if (m_orientation == VERTICAL)
747 layout->ResizeLayout(layout->Rows() - CELLS_PER_BUTTON, 1);
748 else
749 layout->ResizeLayout(1, layout->Columns() - CELLS_PER_BUTTON);
750 }
751
752 if (index == m_checked_button)
753 m_checked_button = NO_BUTTON;
754 else if (index <= m_checked_button)
755 --m_checked_button;
756 Reconnect();
757 }
758
ExpandButtons(bool expand)759 void RadioButtonGroup::ExpandButtons(bool expand)
760 {
761 if (expand != m_expand_buttons) {
762 std::size_t old_checked_button = m_checked_button;
763 std::vector<std::shared_ptr<StateButton>> buttons(m_button_slots.size());
764 while (!m_button_slots.empty()) {
765 auto button = m_button_slots.back().button;
766 buttons[m_button_slots.size() - 1] = button;
767 RemoveButton(button.get());
768 }
769 m_expand_buttons = expand;
770 for (auto& button : buttons) {
771 AddButton(button);
772 }
773 SetCheck(old_checked_button);
774 }
775 }
776
ExpandButtonsProportionally(bool proportional)777 void RadioButtonGroup::ExpandButtonsProportionally(bool proportional)
778 {
779 if (proportional != m_expand_buttons_proportionally) {
780 std::size_t old_checked_button = m_checked_button;
781 std::vector<std::shared_ptr<StateButton>> buttons(m_button_slots.size());
782 while (!m_button_slots.empty()) {
783 auto& button = m_button_slots.back().button;
784 buttons[m_button_slots.size() - 1] = button;
785 RemoveButton(button.get());
786 }
787 m_expand_buttons_proportionally = proportional;
788 for (auto& button : buttons) {
789 AddButton(button);
790 }
791 SetCheck(old_checked_button);
792 }
793 }
794
RenderOutline(bool render_outline)795 void RadioButtonGroup::RenderOutline(bool render_outline)
796 { m_render_outline = render_outline; }
797
ButtonSlots() const798 const std::vector<RadioButtonGroup::ButtonSlot>& RadioButtonGroup::ButtonSlots() const
799 { return m_button_slots; }
800
ConnectSignals()801 void RadioButtonGroup::ConnectSignals()
802 {
803 for (std::size_t i = 0; i < m_button_slots.size(); ++i) {
804 m_button_slots[i].connection = m_button_slots[i].button->CheckedSignal.connect([this, i](bool checked) {
805 if (checked)
806 this->SetCheckImpl(i, true);
807 else
808 this->m_button_slots[i].button->SetCheck(true);
809 });
810 }
811 SetCheck(m_checked_button);
812 }
813
SetCheckImpl(std::size_t index,bool signal)814 void RadioButtonGroup::SetCheckImpl(std::size_t index, bool signal)
815 {
816 assert(m_checked_button == NO_BUTTON || m_checked_button < m_button_slots.size());
817 if (m_checked_button != NO_BUTTON)
818 m_button_slots[m_checked_button].button->SetCheck(false);
819 if (index != NO_BUTTON)
820 m_button_slots[index].button->SetCheck(true);
821 m_checked_button = index;
822 if (signal)
823 ButtonChangedSignal(m_checked_button);
824 }
825
Reconnect()826 void RadioButtonGroup::Reconnect()
827 {
828 for (ButtonSlot& button_slot : m_button_slots) {
829 button_slot.connection.disconnect();
830 }
831 ConnectSignals();
832 }
833