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