Simple GUI Library
default_skins.cpp
Go to the documentation of this file.
1/**
2 * @author Nikita Mochalov (github.com/tralf-strues)
3 * @file default_skins.cpp
4 * @date 2021-11-08
5 *
6 * @copyright Copyright (c) 2021
7 */
8
15
16namespace Sgl
17{
18namespace DefaultSkins
19{
20 Sml::Font* g_DefaultFont = nullptr;
21
22 #define ASSERT_CATEGORY(eventClass) \
23 assert(event != nullptr); \
24 assert(event->isInCategory(eventClass::getStaticCategory()))
25
26 #define ASSERT_EITHER_CATEGORY(eventClass1, eventClass2) \
27 assert(event != nullptr); \
28 assert(event->isInCategory(eventClass1::getStaticCategory() || \
29 eventClass2::getStaticCategory()))
30
31 #define INVALID_EVENT_TYPE(handler) LOG_LIB_ERROR("Invalid event type in " #handler)
32
33 //------------------------------------------------------------------------------
34 // ButtonBaseSkin
35 //------------------------------------------------------------------------------
36 ButtonBaseSkin::StaticStyle::StaticStyle(const Insets& paddingLabelOnly,
37 const Insets& paddingIconOnly,
38 const Insets& paddingIconAndLabel,
39 int32_t margin,
40 const Border& border)
41 : paddingLabelOnly(paddingLabelOnly),
42 paddingIconOnly(paddingIconOnly),
43 paddingIconAndLabel(paddingIconAndLabel),
44 margin(margin),
45 border(border) {}
46
47 ButtonBaseSkin::StaticStyle::StaticStyle(const Insets& padding, int32_t margin, const Border& border)
48 : StaticStyle(padding, padding, padding, margin, border) {}
49
51 {
52 public:
53 DEFINE_STATIC_LISTENED_EVENT_TYPES(MouseEnteredEvent::getStaticType(),
54 MouseExitedEvent::getStaticType(),
55 Sml::MouseButtonPressedEvent::getStaticType())
56
57 public:
59
60 virtual void onEvent(Sml::Event* event) override
61 {
62 ASSERT_CATEGORY(Sml::MouseEvent);
63 ButtonBaseSkin& skin = dynamic_cast<ButtonBaseSkin&>(*getComponent()->getSkin());
64
65 switch (event->getType())
66 {
67 case MouseEnteredEvent::getStaticType():
68 {
69 skin.applyInteractionStyle(ButtonBaseSkin::InteractionStyle::Type::HOVERED);
70 break;
71 }
72
73 case MouseExitedEvent::getStaticType():
74 {
75 skin.applyInteractionStyle(ButtonBaseSkin::InteractionStyle::Type::IDLE);
76 break;
77 }
78
79 case Sml::MouseButtonPressedEvent::getStaticType():
80 {
81 if (getComponent()->getOnAction() != nullptr)
82 {
83 // FIXME: Add event queue!
84 ActionEvent event{getComponent()};
85 getComponent()->getOnAction()->onAction(&event);
86 }
87
88 break;
89 }
90
91 default:
92 {
93 INVALID_EVENT_TYPE(ButtonBaseSkinEventListener);
94 break;
95 }
96 }
97
98 event->consume();
99 }
100 };
101
102 ButtonBaseSkin::ButtonBaseSkin(const StaticStyle* staticStyle,
103 const InteractionStyle* idleStyle,
104 const InteractionStyle* hoveredStyle,
105 const InteractionStyle* pressedStyle)
106 : m_StaticStyle(staticStyle),
107 m_IdleStyle(idleStyle),
108 m_HoveredStyle(hoveredStyle),
109 m_PressedStyle(pressedStyle),
110 m_Text(*g_DefaultFont, nullptr) {}
111
112 ButtonBaseSkin::ButtonBaseSkin(Button* button,
113 const StaticStyle* staticStyle,
114 const InteractionStyle* idleStyle,
115 const InteractionStyle* hoveredStyle,
116 const InteractionStyle* pressedStyle)
117 : ButtonBaseSkin(staticStyle, idleStyle, hoveredStyle, pressedStyle)
118 {
119 assert(button);
120 attach(button);
121 }
122
123 void ButtonBaseSkin::attach(Button* button)
124 {
125 m_Button = button;
126
127 m_Handler = new ButtonBaseSkinEventListener(m_Button);
128 m_Button->getEventDispatcher()->attachHandler(ButtonBaseSkinEventListener::EVENT_TYPES, m_Handler);
129
130 m_Button->addChild(&m_Icon);
131 m_Button->addChild(&m_Text);
132
133 applyStaticStyle();
134 applyInteractionStyle(InteractionStyle::Type::IDLE);
135 }
136
138 {
139 m_Button->getEventDispatcher()->detachHandler(m_Handler);
140 delete m_Handler;
141
142 m_Button->removeChild(&m_Icon);
143 m_Button->removeChild(&m_Text);
144 }
145
146 void ButtonBaseSkin::prerenderControl()
147 {
148 if (m_Button->getBackground() != nullptr)
149 {
150 Background::fillArea(m_Button->getBackground(), m_Button->getOriginBounds());
151 }
152
153 if (m_Button->getBorder() != nullptr)
154 {
155 Border::encloseArea(m_Button->getBorder(), m_Button->getOriginBounds());
156 }
157 }
158
159 const Control* ButtonBaseSkin::getControl() const { return m_Button; }
160 Control* ButtonBaseSkin::getModifiableControl() { return m_Button; }
161
162 int32_t ButtonBaseSkin::computePrefHeight(int32_t width) const
163 {
164 Insets insets = m_Button->getInsets();
165
166 return insets.top + insets.bottom +
167 std::max(m_Text.computePrefHeight(width), m_Icon.computePrefHeight(width));
168 }
169
170 int32_t ButtonBaseSkin::computePrefWidth(int32_t height) const
171 {
172 Insets insets = m_Button->getInsets();
173
174 int32_t textPrefWidth = m_Text.computePrefWidth(height);
175 int32_t iconPrefWidth = m_Icon.computePrefWidth(height);
176
177 return insets.left + insets.right +
178 ((textPrefWidth != 0 && iconPrefWidth != 0) ? getMargin() : 0) +
179 textPrefWidth + iconPrefWidth;
180 }
181
182 void ButtonBaseSkin::layoutChildren()
183 {
184 Sml::Rectangle<int32_t> contentArea = m_Button->getContentArea();
185
186 m_Text.setString(m_Button->getLabel());
187 m_Icon.setImage(m_Button->getIcon());
188 applyStaticStyle(); // FIXME: move somewhere else, no need to update this each layout pass (or not?..)
189
190 m_Text.setLayoutWidth(m_Text.computePrefWidth());
191 m_Text.setLayoutHeight(m_Text.computePrefHeight());
192
193 int32_t iconHeight = std::min(m_Icon.computePrefHeight(), contentArea.height);
194 int32_t iconWidth = m_Icon.computePrefWidth(iconHeight);
195 m_Icon.setLayoutWidth(iconWidth);
196 m_Icon.setLayoutHeight(iconHeight);
197
198 int32_t centerY = contentArea.pos.y + contentArea.height / 2;
199
200 m_Icon.setLayoutX(contentArea.pos.x);
201 m_Icon.setLayoutY(centerY - m_Icon.getLayoutHeight() / 2);
202
203 m_Text.setLayoutX(m_Icon.getLayoutX() + m_Icon.getLayoutWidth());
204 if (m_Icon.getLayoutWidth() != 0)
205 {
206 m_Text.setLayoutX(m_Text.getLayoutX() + getMargin());
207 }
208
209 m_Text.setLayoutY(centerY - m_Text.getLayoutHeight() / 2);
210 }
211
212 int32_t ButtonBaseSkin::getMargin() const
213 {
214 return m_StaticStyle == nullptr ? 0 : m_StaticStyle->margin;
215 }
216
217 void ButtonBaseSkin::applyStaticStyle()
218 {
219 if (m_StaticStyle != nullptr)
220 {
221 bool isLabelSet = m_Button->getLabel() != nullptr;
222 bool isIconSet = m_Button->getIcon() != nullptr;
223
224 if (isLabelSet && !isIconSet)
225 {
226 m_Button->setPadding(m_StaticStyle->paddingLabelOnly);
227 }
228 else if (!isLabelSet && isIconSet)
229 {
230 m_Button->setPadding(m_StaticStyle->paddingIconOnly);
231 }
232 else if (isLabelSet && isIconSet)
233 {
234 m_Button->setPadding(m_StaticStyle->paddingIconAndLabel);
235 }
236
237 m_Button->setBorder(&m_StaticStyle->border);
238 }
239 else
240 {
241 m_Button->setPadding(Insets::EMPTY);
242 m_Button->setBorder(nullptr);
243 }
244 }
245
246 void ButtonBaseSkin::applyInteractionStyle(InteractionStyle::Type type)
247 {
248 const InteractionStyle* style = nullptr;
249 switch (type)
250 {
251 case InteractionStyle::Type::IDLE: { style = m_IdleStyle; break; }
252 case InteractionStyle::Type::HOVERED: { style = m_HoveredStyle; break; }
253 case InteractionStyle::Type::PRESSED: { style = m_PressedStyle; break; }
254 }
255
256 if (style != nullptr)
257 {
258 m_Text.setColor(style->foreground);
259 m_Button->setBackground(style->background);
260 }
261 else
262 {
263 m_Text.setColor(Sml::COLOR_BLACK);
264 m_Button->setBackground(nullptr);
265 }
266 }
267
268 //------------------------------------------------------------------------------
269 // ButtonPlaneSkin
270 //------------------------------------------------------------------------------
271 ButtonPlaneSkin::ButtonPlaneSkin()
272 : ButtonBaseSkin(nullptr, nullptr, nullptr, nullptr) {}
273
274 ButtonPlaneSkin::ButtonPlaneSkin(Sgl::Button* button)
275 : ButtonBaseSkin(button, nullptr, nullptr, nullptr, nullptr) {}
276
277 //------------------------------------------------------------------------------
278 // ButtonSkin
279 //------------------------------------------------------------------------------
280 const Insets ButtonSkin::PADDING_LABEL_ONLY = Insets{5, 10};
281 const Insets ButtonSkin::PADDING_ICON_ONLY = Insets{5};
282 const Insets ButtonSkin::PADDING_LABEL_AND_ICON = Insets{5, 10};
283 const int32_t ButtonSkin::MARGIN = 5;
284 const Border ButtonSkin::BORDER = Border{1, 0xE9'E9'E9'FF};
285 const ButtonBaseSkin::StaticStyle ButtonSkin::STATIC_STYLE = {PADDING_LABEL_ONLY,
286 PADDING_ICON_ONLY,
287 PADDING_LABEL_AND_ICON,
288 MARGIN,
289 BORDER};
290
291 /* Idle */
292 const Sml::Color ButtonSkin::IDLE_FOREGROUND = Sml::COLOR_BLACK;
293 const ColorFill ButtonSkin::IDLE_BACKGROUND_FILL = {0xF5'F5'F5'FF};
294 const Background ButtonSkin::IDLE_BACKGROUND = {&IDLE_BACKGROUND_FILL};
295 const ButtonBaseSkin::InteractionStyle ButtonSkin::IDLE_STYLE = {IDLE_FOREGROUND,
296 &IDLE_BACKGROUND};
297
298 /* Hovered */
299 const Sml::Color ButtonSkin::HOVERED_FOREGROUND = Sml::COLOR_WHITE;
300 const ColorFill ButtonSkin::HOVERED_BACKGROUND_FILL = {0x25'92'FF'FF};
301 const Background ButtonSkin::HOVERED_BACKGROUND = {&HOVERED_BACKGROUND_FILL};
302 const ButtonBaseSkin::InteractionStyle ButtonSkin::HOVERED_STYLE = {HOVERED_FOREGROUND,
303 &HOVERED_BACKGROUND};
304
305 ButtonSkin::ButtonSkin()
306 : ButtonBaseSkin(&STATIC_STYLE, &IDLE_STYLE, &HOVERED_STYLE, nullptr) {}
307
308 ButtonSkin::ButtonSkin(Sgl::Button* button)
309 : ButtonBaseSkin(button, &STATIC_STYLE, &IDLE_STYLE, &HOVERED_STYLE, nullptr) {}
310
311 //------------------------------------------------------------------------------
312 // MenuItemSkin
313 //------------------------------------------------------------------------------
314 const Insets MenuItemSkin::PADDING = Insets{5, 10};
315 const int32_t MenuItemSkin::MARGIN = 5;
316 const Border MenuItemSkin::BORDER = Border{0, 0};
317 const ButtonBaseSkin::StaticStyle MenuItemSkin::STATIC_STYLE = {PADDING, MARGIN, BORDER};
318
319 /* Idle */
320 const Sml::Color MenuItemSkin::IDLE_FOREGROUND = Sml::COLOR_BLACK;
321 const ColorFill MenuItemSkin::IDLE_BACKGROUND_FILL = {Sml::COLOR_TRANSPARENT};
322 const Background MenuItemSkin::IDLE_BACKGROUND = {&IDLE_BACKGROUND_FILL};
323 const ButtonBaseSkin::InteractionStyle MenuItemSkin::IDLE_STYLE = {IDLE_FOREGROUND,
324 nullptr};
325
326 /* Hovered */
327 const Sml::Color MenuItemSkin::HOVERED_FOREGROUND = 0xEF'F8'FF'FF;
328 const ColorFill MenuItemSkin::HOVERED_BACKGROUND_FILL = {0x5A'B9'FF'FF};
329 const Background MenuItemSkin::HOVERED_BACKGROUND = {&HOVERED_BACKGROUND_FILL};
330 const ButtonBaseSkin::InteractionStyle MenuItemSkin::HOVERED_STYLE = {HOVERED_FOREGROUND,
331 &HOVERED_BACKGROUND};
332
333 MenuItemSkin::MenuItemSkin()
334 : ButtonBaseSkin(&STATIC_STYLE, &IDLE_STYLE, &HOVERED_STYLE, nullptr) {}
335
336 MenuItemSkin::MenuItemSkin(Sgl::Button* button)
337 : ButtonBaseSkin(button, &STATIC_STYLE, &IDLE_STYLE, &HOVERED_STYLE, nullptr) {}
338
339 //------------------------------------------------------------------------------
340 // SliderSkin
341 //------------------------------------------------------------------------------
342 const ShadowSpecification SliderSkin::KNOB_SHADOW = {{0, 0}, {1.1, 1.1}, 3, 0x00'00'00'88};
343 const ColorFill SliderSkin::NOT_SELECTED_FILL = {0xE0'E0'E0'FF};
344 const ColorFill SliderSkin::SELECTED_FILL = {0x32'73'F6'FF};
345 const Sml::Color SliderSkin::KNOB_COLOR = 0x27'5B'E1'FF;
346 const int32_t SliderSkin::THICKNESS = 6;
347 const int32_t SliderSkin::KNOB_SIZE_ALONG = 6;
348 const int32_t SliderSkin::KNOB_SIZE_ACROSS = 11;
349
351 {
352 public:
354
355 void updateValue(const Sml::Vec2i& mousePos)
356 {
357 Slider& slider = *getComponent();
358 SliderSkin& skin = dynamic_cast<SliderSkin&>(*slider.getSkin());
359
360 Sml::Vec2i localPos = slider.computeSceneToLocalPos(mousePos);
361
362 int32_t posAlong = slider.getOrientation() == Orientation::HORIZONTAL ? localPos.x : localPos.y;
363 int32_t lineLength = slider.getOrientation() == Orientation::HORIZONTAL ? skin.getLineRect().width :
364 skin.getLineRect().height;
365
366 slider.setValue(slider.getRangeMin() +
367 (static_cast<float>(posAlong - SliderSkin::KNOB_SIZE_ALONG / 2) /
368 static_cast<float>(lineLength - SliderSkin::KNOB_SIZE_ALONG)) *
369 (slider.getRangeMax() - slider.getRangeMin()));
370 }
371
372 virtual void onDragStart(DragStartEvent* event)
373 {
374 LOG_LIB_INFO("SliderSkinDragListener::onDragStart() called");
375
376 updateValue(Sml::Vec2i(event->getX(), event->getY()));
377 }
378
379 virtual void onDragMove(DragMoveEvent* event)
380 {
381 LOG_LIB_INFO("SliderSkinDragListener::onDragMove() called");
382
383 updateValue(Sml::Vec2i(event->getX(), event->getY()));
384 }
385 };
386
388 {
389 public:
390 DEFINE_STATIC_LISTENED_EVENT_TYPES(Sml::MouseButtonPressedEvent::getStaticType())
391
392 public:
394
395 virtual void onEvent(Sml::Event* event) override
396 {
397 LOG_LIB_INFO("SliderSkinMousePressListener called");
398 getComponent()->requestDrag();
399 }
400 };
401
402 SliderSkin::SliderSkin(const Fill* notSelectedFill,
403 const Fill* selectedFill,
404 Sml::Color knobColor,
405 int32_t thickness,
406 int32_t knobSizeAlong,
407 int32_t knobSizeAcross)
408 : m_NotSelectedFill(notSelectedFill),
409 m_SelectedFill(selectedFill),
410 m_Thickness(thickness),
411 m_KnobSizeAlong(knobSizeAlong),
412 m_KnobSizeAcross(knobSizeAcross),
413 m_KnobRect(new Sgl::Rectangle())
414 {
415 m_KnobRect->setFillColor(knobColor);
416 m_KnobRect->setShadow(&KNOB_SHADOW);
417 }
418
419 SliderSkin::SliderSkin(Slider* slider) : SliderSkin(&NOT_SELECTED_FILL, &SELECTED_FILL)
420 {
421 assert(slider);
422 attach(slider);
423 }
424
426 {
427 m_Slider->removeChild(m_KnobRect);
428 delete m_KnobRect;
429
430 m_Slider->getEventDispatcher()->detachHandler(m_MousePressListener);
431 delete m_MousePressListener;
432
433 m_Slider->getEventDispatcher()->detachHandler(m_DragListener);
434 delete m_DragListener;
435 }
436
437 void SliderSkin::attach(Slider* slider)
438 {
439 assert(slider);
440
441 m_Slider = slider;
442 m_Slider->addChild(m_KnobRect);
443
444 m_MousePressListener = new SliderSkinMousePressListener(m_Slider);
445 m_Slider->getEventDispatcher()->attachHandler(SliderSkinMousePressListener::EVENT_TYPES, m_MousePressListener);
446
447 m_DragListener = new SliderSkinDragListener(m_Slider);
448 m_Slider->getEventDispatcher()->attachHandler(SliderSkinDragListener::EVENT_TYPES, m_DragListener);
449 }
450
451 Component* SliderSkin::getHitComponent(int32_t x, int32_t y)
452 {
453 Sml::Rectangle<int32_t> translatedRect = getLineRect();
454 translatedRect.pos += m_Slider->getLayoutPos();
455
456 if (Sml::isPointInsideRectangle({x, y}, translatedRect))
457 {
458 return m_Slider;
459 }
460
461 translatedRect = getKnobRect();
462 translatedRect.pos += m_Slider->getLayoutPos();
463 if (Sml::isPointInsideRectangle({x, y}, translatedRect))
464 {
465 return m_Slider;
466 }
467
468 return nullptr;
469 }
470
471 void SliderSkin::prerenderControl()
472 {
473 Sml::Rectangle<int32_t> lineRect = getLineRect();
474
475 if (m_NotSelectedFill != nullptr)
476 {
477 m_NotSelectedFill->fillArea(lineRect, m_Slider->getOriginBounds());
478 }
479
480 if (m_Slider->getOrientation() == Orientation::HORIZONTAL)
481 {
482 lineRect.width *= getPercentage();
483 }
484 else
485 {
486 lineRect.height *= getPercentage();
487 }
488
489 if (m_SelectedFill != nullptr)
490 {
491 m_SelectedFill->fillArea(lineRect, m_Slider->getOriginBounds());
492 }
493 }
494
495 const Control* SliderSkin::getControl() const { return m_Slider; }
496 Control* SliderSkin::getModifiableControl() { return m_Slider; }
497
498 int32_t SliderSkin::computePrefWidth(int32_t height) const
499 {
500 if (m_Slider->getOrientation() == Orientation::HORIZONTAL)
501 {
502 return m_KnobSizeAlong;
503 }
504
505 return m_KnobSizeAcross;
506 }
507
508 int32_t SliderSkin::computePrefHeight(int32_t width) const
509 {
510 if (m_Slider->getOrientation() == Orientation::VERTICAL)
511 {
512 return m_KnobSizeAlong;
513 }
514
515 return m_KnobSizeAcross;
516 }
517
518 void SliderSkin::layoutChildren()
519 {
520 Sml::Rectangle<int32_t> knobRect = getKnobRect();
521
522 m_KnobRect->setLayoutWidth(knobRect.width);
523 m_KnobRect->setLayoutHeight(knobRect.height);
524 m_KnobRect->setLayoutX(knobRect.pos.x);
525 m_KnobRect->setLayoutY(knobRect.pos.y);
526 }
527
528 int32_t SliderSkin::getThickness() const { return m_Thickness; }
529 void SliderSkin::setThickness(int32_t thickness) { m_Thickness = thickness; }
530
531 int32_t SliderSkin::getKnobSizeAlong() const { return m_KnobSizeAlong; }
532 void SliderSkin::setKnobSizeAlong(int32_t knobSizeAlong) { m_KnobSizeAlong = knobSizeAlong; }
533
534 int32_t SliderSkin::getKnobSizeAcross() const { return m_KnobSizeAcross; }
535 void SliderSkin::setKnobSizeAcross(int32_t knobSizeAcross) { m_KnobSizeAcross = knobSizeAcross; }
536
537 void SliderSkin::setKnobShadow(const ShadowSpecification* shadow) { m_KnobRect->setShadow(shadow); }
538
539 Sml::Rectangle<int32_t> SliderSkin::getLineRect()
540 {
541 if (m_Slider->getOrientation() == Orientation::HORIZONTAL)
542 {
543 Sml::Rectangle<int32_t> originBounds = m_Slider->getOriginBounds();
544 int32_t centerY = originBounds.height / 2;
545
546 return Sml::Rectangle<int32_t>{0, centerY - m_Thickness / 2, originBounds.width, m_Thickness};
547 }
548 else
549 {
550 Sml::Rectangle<int32_t> originBounds = m_Slider->getOriginBounds();
551 int32_t centerX = originBounds.width / 2;
552
553 return Sml::Rectangle<int32_t>{centerX - m_Thickness / 2, 0, m_Thickness, originBounds.height};
554 }
555 }
556
557 Sml::Rectangle<int32_t> SliderSkin::getKnobRect()
558 {
559 if (m_Slider->getOrientation() == Orientation::HORIZONTAL)
560 {
561 Sml::Rectangle<int32_t> originBounds = m_Slider->getOriginBounds();
562 int32_t centerY = originBounds.height / 2;
563 int32_t knobCenterX = m_KnobSizeAlong / 2 + getPercentage() * (originBounds.width - m_KnobSizeAlong);
564
565 return Sml::Rectangle<int32_t>{knobCenterX - m_KnobSizeAlong / 2, centerY - m_KnobSizeAcross / 2,
566 m_KnobSizeAlong, m_KnobSizeAcross};
567 }
568 else
569 {
570 Sml::Rectangle<int32_t> originBounds = m_Slider->getOriginBounds();
571 int32_t centerX = originBounds.width / 2;
572 int32_t knobCenterY = m_KnobSizeAlong / 2 + getPercentage() * (originBounds.height - m_KnobSizeAlong);
573
574 return Sml::Rectangle<int32_t>{centerX - m_KnobSizeAcross / 2, knobCenterY - m_KnobSizeAlong / 2,
575 m_KnobSizeAcross, m_KnobSizeAlong};
576 }
577 }
578
579 float SliderSkin::getPercentage()
580 {
581 return (m_Slider->getValue() - m_Slider->getRangeMin()) / (m_Slider->getRangeMax() - m_Slider->getRangeMin());
582 }
583
584 //------------------------------------------------------------------------------
585 // ScrollBarSkin
586 //------------------------------------------------------------------------------
587 const Sml::Color ScrollBarSkin::KNOB_COLOR = 0xF5'F5'F5'FF;
588 // const ShadowSpecification ScrollBarSkin::KNOB_SHADOW = {{0, 0}, {1.07, 1.07}, 3, 0xCC'CC'CC'88};
589
590 ScrollBarSkin::ScrollBarSkin(ScrollBar* scrollBar)
591 : m_Box(new BoxContainer()),
592 m_SliderSkin(new SliderSkin(&SliderSkin::NOT_SELECTED_FILL, nullptr, KNOB_COLOR)),
593 m_Slider(new Slider(m_SliderSkin, 0, 1)),
594 m_DecrementButton(new Button(new ButtonPlaneSkin())),
595 m_IncrementButton(new Button(new ButtonPlaneSkin()))
596 {
597 assert(scrollBar);
598
599 m_Box->addChildren(m_DecrementButton, m_Slider, m_IncrementButton);
600 m_Box->setGrowPriority(m_Slider, BoxContainer::GrowPriority::ALWAYS);
601 m_Box->setFillAcross(true);
602
603 m_SliderSkin->setKnobShadow(nullptr);
604
605 attach(scrollBar);
606 }
607
609 {
610 m_ScrollBar->removeChild(m_Box);
611 delete m_Box;
612 }
613
614 void ScrollBarSkin::attach(ScrollBar* scrollBar)
615 {
616 assert(scrollBar);
617 m_ScrollBar = scrollBar;
618
619 if (m_ScrollBar->getOrientation() == Orientation::HORIZONTAL)
620 {
621 m_Box->setDirection(BoxContainer::Direction::LEFT_TO_RIGHT);
622
623 m_DecrementButton->setIcon(new Image("res/sgl/arrow_left.png", ImageFormat::PNG));
624 m_IncrementButton->setIcon(new Image("res/sgl/arrow_right.png", ImageFormat::PNG));
625 }
626 else
627 {
628 m_Box->setDirection(BoxContainer::Direction::TOP_TO_BOTTOM);
629
630 m_DecrementButton->setIcon(new Image("res/sgl/arrow_up.png", ImageFormat::PNG));
631 m_IncrementButton->setIcon(new Image("res/sgl/arrow_down.png", ImageFormat::PNG));
632 }
633
634 m_ScrollBar->addChild(m_Box);
635
636 class ChangeValueButtonListener : public ActionListener<Button>
637 {
638 public:
639 ChangeValueButtonListener(Button* button, ScrollBar* scrollBar, float multiplier)
640 : ActionListener<Button>(button), m_ScrollBar(scrollBar), m_Multiplier(multiplier) {}
641
642 virtual void onAction(ActionEvent* event) override
643 {
644 m_ScrollBar->setValue(m_ScrollBar->getValue() + m_ScrollBar->getIncrement() * m_Multiplier);
645 }
646
647 private:
648 ScrollBar* m_ScrollBar = nullptr;
649 float m_Multiplier = 0;
650 };
651
652 m_DecrementButton->setOnAction(new ChangeValueButtonListener(m_DecrementButton, m_ScrollBar, -1));
653 m_IncrementButton->setOnAction(new ChangeValueButtonListener(m_IncrementButton, m_ScrollBar, 1));
654
655 class SliderPropertyChangeListener : public Sml::PropertyChangeListener<float>
656 {
657 public:
658 SliderPropertyChangeListener(ScrollBar* scrollBar) : m_ScrollBar(scrollBar) {}
659
660 virtual void onPropertyChange(Sml::PropertyChangeEvent<float>* event) override
661 {
662 m_ScrollBar->setValue(event->getNewValue());
663 }
664
665 private:
666 ScrollBar* m_ScrollBar = nullptr;
667 };
668
669 m_Slider->addOnPropertyChange(new SliderPropertyChangeListener(m_ScrollBar));
670 }
671
672 const Control* ScrollBarSkin::getControl() const { return m_ScrollBar; }
673 Control* ScrollBarSkin::getModifiableControl() { return m_ScrollBar; }
674
675 void ScrollBarSkin::layoutChildren()
676 {
677 int32_t layoutWidth = m_ScrollBar->getLayoutWidth();
678 int32_t layoutHeight = m_ScrollBar->getLayoutHeight();
679
680 m_Box->setLayoutX(0);
681 m_Box->setLayoutY(0);
682 m_Box->setLayoutWidth(layoutWidth);
683 m_Box->setLayoutHeight(layoutHeight);
684
685 m_Slider->setRangeMin(m_ScrollBar->getRangeMin());
686 m_Slider->setRangeMax(m_ScrollBar->getRangeMax());
687 m_Slider->setValue(m_ScrollBar->getValue());
688 m_Slider->setOrientation(m_ScrollBar->getOrientation());
689
690 if (m_ScrollBar->getOrientation() == Orientation::HORIZONTAL)
691 {
692 m_SliderSkin->setThickness(layoutHeight);
693 m_SliderSkin->setKnobSizeAcross(layoutHeight - 4);
694 m_SliderSkin->setKnobSizeAlong(m_Slider->getLayoutWidth() * m_ScrollBar->getVisibleRange() /
695 (m_ScrollBar->getRangeMax() - m_ScrollBar->getRangeMin()));
696 }
697 else
698 {
699 m_SliderSkin->setThickness(layoutWidth);
700 m_SliderSkin->setKnobSizeAcross(layoutWidth - 4);
701 m_SliderSkin->setKnobSizeAlong(m_Slider->getLayoutHeight() * m_ScrollBar->getVisibleRange() /
702 (m_ScrollBar->getRangeMax() - m_ScrollBar->getRangeMin()));
703 }
704 }
705
706 //------------------------------------------------------------------------------
707 // ScrollPaneSkin
708 //------------------------------------------------------------------------------
709 class HorizontalScrollListener : public Sml::PropertyChangeListener<float>
710 {
711 public:
712 HorizontalScrollListener(ScrollPaneSkin* skin) : m_Skin(skin) {}
713
714 virtual void onPropertyChange(Sml::PropertyChangeEvent<float>* event) override
715 {
716 ScrollPane* scrollPane = dynamic_cast<ScrollPane*>(m_Skin->getModifiableControl());
717
718 if (scrollPane->getContent() != nullptr)
719 {
720 // int32_t delta = (scrollPane->getContent()->getLayoutWidth() - scrollPane->getViewportWidth()) *
721 // (event->getNewValue() - event->getOldValue());
722
723 // scrollPane->setViewportWidth(scrollPane->getViewportWidth() + delta);
724 scrollPane->setViewportX(event->getNewValue() * (scrollPane->getContent()->getLayoutWidth() - scrollPane->getViewportWidth()));
725 }
726 }
727
728 private:
729 ScrollPaneSkin* m_Skin = nullptr;
730 };
731
732 class VerticalScrollListener : public Sml::PropertyChangeListener<float>
733 {
734 public:
735 VerticalScrollListener(ScrollPaneSkin* skin) : m_Skin(skin) {}
736
737 virtual void onPropertyChange(Sml::PropertyChangeEvent<float>* event) override
738 {
739 ScrollPane* scrollPane = dynamic_cast<ScrollPane*>(m_Skin->getModifiableControl());
740
741 if (scrollPane->getContent() != nullptr)
742 {
743 // int32_t delta = (scrollPane->getContent()->getLayoutHeight() - scrollPane->getViewportHeight()) *
744 // (event->getNewValue() - event->getOldValue());
745
746 // // scrollPane->setViewportHeight(scrollPane->getViewportHeight() + delta);
747 // scrollPane->setViewportY(scrollPane->getViewportY() + delta);
748 scrollPane->setViewportY(event->getNewValue() * (scrollPane->getContent()->getLayoutHeight() - scrollPane->getViewportHeight()));
749 }
750 }
751
752 private:
753 ScrollPaneSkin* m_Skin = nullptr;
754 };
755
756 ScrollPaneSkin::ScrollPaneSkin(ScrollPane* scrollPane)
757 : m_HorizontalScrollBar(new Sgl::ScrollBar(Orientation::HORIZONTAL, 0, 1)),
758 m_VerticalScrollBar(new Sgl::ScrollBar(Orientation::VERTICAL, 0, 1))
759 {
760 assert(scrollPane);
761
762 m_HorizontalScrollBar->setIncrement(0.1);
763 m_VerticalScrollBar->setIncrement(0.1);
764
765 attach(scrollPane);
766 }
767
769 {
770 m_ScrollPane->removeChild(m_ScrollPane->getContent());
771 }
772
773 void ScrollPaneSkin::attach(ScrollPane* scrollPane)
774 {
775 assert(scrollPane);
776 m_ScrollPane = scrollPane;
777
778 m_ScrollPane->addChildren(m_HorizontalScrollBar, m_VerticalScrollBar);
779
780 m_HorizontalScrollBar->setOnPropertyChange(new HorizontalScrollListener(this));
781 m_VerticalScrollBar->setOnPropertyChange(new VerticalScrollListener(this));
782 }
783
784 Component* ScrollPaneSkin::getHitComponent(int32_t x, int32_t y)
785 {
786 Sml::Vec2i posInPane = Sml::Vec2i(x, y) - m_ScrollPane->getLayoutPos();
787
788 if (m_ScrollPane->getContent() != nullptr && Sml::isPointInsideRectangle(posInPane, computeContentRegion()))
789 {
790 return m_ScrollPane->getContent()->getHitComponent(x, y);
791 }
792
793 if (Sml::isPointInsideRectangle(posInPane, m_HorizontalScrollBar->getLayoutBounds()))
794 {
795 const Sml::Vec2i& layoutPos = m_HorizontalScrollBar->getLayoutPos();
796 return m_HorizontalScrollBar->getHitComponent(layoutPos.x + x, layoutPos.y + y);
797 }
798
799 if (Sml::isPointInsideRectangle(posInPane, m_VerticalScrollBar->getLayoutBounds()))
800 {
801 const Sml::Vec2i& layoutPos = m_VerticalScrollBar->getLayoutPos();
802 return m_VerticalScrollBar->getHitComponent(layoutPos.x + x, layoutPos.y + y);
803 }
804
805 if (Sml::isPointInsideRectangle(posInPane, m_ScrollPane->getLayoutBounds()))
806 {
807 return m_ScrollPane;
808 }
809
810 return nullptr;
811 }
812
813 void ScrollPaneSkin::prerenderControl()
814 {
815 if (m_ScrollPane->getContent() == nullptr)
816 {
817 return;
818 }
819
820 updateRenderedContentTexture();
821
822 if (m_RenderedContentTexture != nullptr)
823 {
824 Sml::Rectangle<int32_t> snapshotRegion = computeContentRegion();
825 m_RenderedContentTexture->copyTo(m_ScrollPane->getSnapshot(), &snapshotRegion, &m_ScrollPane->getViewport());
826 }
827 }
828
829 const Control* ScrollPaneSkin::getControl() const { return m_ScrollPane; }
830 Control* ScrollPaneSkin::getModifiableControl() { return m_ScrollPane; }
831
832 int32_t ScrollPaneSkin::computePrefWidth(int32_t height) const
833 {
834 return m_VerticalScrollBar->computePrefWidth(height) +
835 m_ScrollPane->getContent() != nullptr ? m_ScrollPane->getContent()->computePrefWidth(height) : 0;
836 }
837
838 int32_t ScrollPaneSkin::computePrefHeight(int32_t width) const
839 {
840 return m_HorizontalScrollBar->computePrefHeight(width) +
841 m_ScrollPane->getContent() != nullptr ? m_ScrollPane->getContent()->computePrefHeight(width) : 0;
842 }
843
844 void ScrollPaneSkin::layoutChildren()
845 {
846 Sml::Rectangle<int32_t> contentArea = m_ScrollPane->getContentArea();
847
848 int32_t scrollWidth = m_VerticalScrollBar->computePrefWidth();
849 int32_t scrollHeight = m_HorizontalScrollBar->computePrefHeight();
850
851 m_ScrollPane->getContent()->setLayoutWidth(m_ScrollPane->getContent()->computePrefWidth());
852 m_ScrollPane->getContent()->setLayoutHeight(m_ScrollPane->getContent()->computePrefHeight());
853
854 m_VerticalScrollBar->setLayoutWidth(scrollWidth);
855 m_VerticalScrollBar->setLayoutHeight(contentArea.height - scrollHeight);
856 m_VerticalScrollBar->setLayoutX(contentArea.pos.x + contentArea.width - m_VerticalScrollBar->getLayoutWidth());
857 m_VerticalScrollBar->setLayoutY(contentArea.pos.y);
858
859 m_HorizontalScrollBar->setLayoutWidth(contentArea.width - scrollWidth);
860 m_HorizontalScrollBar->setLayoutHeight(scrollHeight);
861 m_HorizontalScrollBar->setLayoutX(contentArea.pos.x);
862 m_HorizontalScrollBar->setLayoutY(contentArea.pos.y + contentArea.height -
863 m_HorizontalScrollBar->getLayoutHeight());
864
865
866 if (m_ScrollPane->getContent() != nullptr)
867 {
868 m_VerticalScrollBar->setVisibleRange(static_cast<float>(m_ScrollPane->getViewportHeight()) /
869 m_ScrollPane->getContent()->getLayoutHeight());
870 m_HorizontalScrollBar->setVisibleRange(static_cast<float>(m_ScrollPane->getViewportWidth()) /
871 m_ScrollPane->getContent()->getLayoutWidth());
872
873 // m_ScrollPane->getContent()->setScene(m_ScrollPane->getScene());
874 m_ScrollPane->getContent()->setInteractable(false);
875 m_ScrollPane->getContent()->setVisible(false);
876 // FIXME: COSTYL!!!
877 m_ScrollPane->removeChildren();
878 m_ScrollPane->addChild(m_ScrollPane->getContent());
879 m_ScrollPane->addChild(m_HorizontalScrollBar);
880 m_ScrollPane->addChild(m_VerticalScrollBar);
881 }
882 else
883 {
884 m_VerticalScrollBar->setVisibleRange(1);
885 m_HorizontalScrollBar->setVisibleRange(1);
886 }
887
888 if (m_PrevContent != m_ScrollPane->getContent())
889 {
890 m_PrevContent = m_ScrollPane->getContent();
891
892 if (m_PrevContent != nullptr)
893 {
894 m_ScrollPane->setViewport({0, 0, computeContentRegion().width, computeContentRegion().height});
895 }
896 }
897 }
898
899 Sml::Rectangle<int32_t> ScrollPaneSkin::computeContentRegion() const
900 {
901 Sml::Rectangle<int32_t> contentArea = m_ScrollPane->getContentArea();
902
903 return {contentArea.pos, contentArea.width - m_VerticalScrollBar->getLayoutWidth(),
904 contentArea.height - m_HorizontalScrollBar->getLayoutHeight()};
905 }
906
907 void ScrollPaneSkin::updateRenderedContentTexture()
908 {
909 Component* content = m_ScrollPane->getContent();
910 int32_t contentWidth = content->getLayoutWidth();
911 int32_t contentHeight = content->getLayoutHeight();
912
913 if (content == nullptr || contentWidth == 0 || contentHeight == 0)
914 {
915 return;
916 }
917
918 if (m_RenderedContentTexture == nullptr ||
919 m_RenderedContentTexture->getWidth() != contentWidth ||
920 m_RenderedContentTexture->getHeight() != contentHeight)
921 {
922 if (m_RenderedContentTexture == nullptr)
923 {
924 delete m_RenderedContentTexture;
925 }
926
927 m_RenderedContentTexture = new Sml::Texture(contentWidth, contentHeight);
928 }
929
930 Sml::Renderer& renderer = Sml::Renderer::getInstance();
931 renderer.pushSetTarget(m_RenderedContentTexture);
932
933 content->render({0, 0, contentWidth, contentHeight});
934
935 renderer.popTarget();
936 }
937}
938}
virtual void dispose() override
Should be called by Control when this skin is replaced.
virtual void dispose() override
Should be called by Control when this skin is replaced.
virtual void dispose() override
Should be called by Control when this skin is replaced.
virtual void dispose() override
Should be called by Control when this skin is replaced.
Definition: fill.h:19
void addChild(Component *child)
Convenience method for adding a child to Parent.
Definition: parent.cpp:129
void removeChildren()
Definition: parent.cpp:155
void removeChild(Component *child)
Convenience method for removing a child to Parent.
Definition: parent.cpp:146