2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
9 #include "GUITextBox.h"
11 #include "GUIInfoManager.h"
12 #include "GUIMessage.h"
13 #include "guilib/GUIComponent.h"
14 #include "guilib/guiinfo/GUIInfoLabels.h"
15 #include "utils/MathUtils.h"
16 #include "utils/StringUtils.h"
17 #include "utils/XBMCTinyXML.h"
21 using namespace KODI::GUILIB
;
23 CGUITextBox::CGUITextBox(int parentID
, int controlID
, float posX
, float posY
, float width
, float height
,
24 const CLabelInfo
& labelInfo
, int scrollTime
,
25 const CLabelInfo
* labelInfoMono
)
26 : CGUIControl(parentID
, controlID
, posX
, posY
, width
, height
)
27 , CGUITextLayout(labelInfo
.font
, true)
35 ControlType
= GUICONTROL_TEXTBOX
;
38 m_scrollTime
= scrollTime
;
40 m_autoScrollDelay
= 3000;
41 m_autoScrollDelayTime
= 0;
42 m_autoScrollRepeatAnim
= NULL
;
44 m_renderHeight
= height
;
46 SetMonoFont(labelInfoMono
->font
);
49 CGUITextBox::CGUITextBox(const CGUITextBox
& from
)
50 : CGUIControl(from
), CGUITextLayout(from
), m_autoScrollCondition(from
.m_autoScrollCondition
)
52 m_pageControl
= from
.m_pageControl
;
53 m_scrollTime
= from
.m_scrollTime
;
54 m_autoScrollTime
= from
.m_autoScrollTime
;
55 m_autoScrollDelay
= from
.m_autoScrollDelay
;
56 m_minHeight
= from
.m_minHeight
;
57 m_renderHeight
= from
.m_renderHeight
;
58 m_autoScrollRepeatAnim
= NULL
;
59 if (from
.m_autoScrollRepeatAnim
)
60 m_autoScrollRepeatAnim
= new CAnimation(*from
.m_autoScrollRepeatAnim
);
61 m_label
= from
.m_label
;
70 m_autoScrollDelayTime
= 0;
71 ControlType
= GUICONTROL_TEXTBOX
;
74 CGUITextBox::~CGUITextBox(void)
76 delete m_autoScrollRepeatAnim
;
77 m_autoScrollRepeatAnim
= NULL
;
80 bool CGUITextBox::UpdateColors(const CGUIListItem
* item
)
82 bool changed
= CGUIControl::UpdateColors(nullptr);
83 changed
|= m_label
.UpdateColors();
88 #define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
90 void CGUITextBox::UpdateInfo(const CGUIListItem
*item
)
92 m_textColor
= m_label
.textColor
;
93 if (!CGUITextLayout::Update(item
? m_info
.GetItemLabel(item
) : m_info
.GetLabel(m_parentID
), m_width
))
94 return; // nothing changed
96 // needed update, so reset to the top of the textbox and update our sizing/page control
100 ResetAutoScrolling();
102 m_itemHeight
= m_font
? m_font
->GetLineHeight() : 10;
103 float textHeight
= m_font
? m_font
->GetTextHeight(m_lines
.size()) : m_itemHeight
* m_lines
.size();
104 float maxHeight
= m_height
? m_height
: textHeight
;
105 m_renderHeight
= m_minHeight
? CLAMP(textHeight
, m_minHeight
, maxHeight
) : m_height
;
106 m_itemsPerPage
= (unsigned int)(m_renderHeight
/ m_itemHeight
);
111 void CGUITextBox::DoProcess(unsigned int currentTime
, CDirtyRegionList
&dirtyregions
)
113 CGUIControl::DoProcess(currentTime
, dirtyregions
);
115 // if not visible, we reset the autoscroll timer and positioning
116 if (!IsVisible() && m_autoScrollTime
)
118 ResetAutoScrolling();
119 m_lastRenderTime
= 0;
126 void CGUITextBox::Process(unsigned int currentTime
, CDirtyRegionList
&dirtyregions
)
128 // update our auto-scrolling as necessary
129 if (m_autoScrollTime
&& m_lines
.size() > m_itemsPerPage
)
131 if (!m_autoScrollCondition
|| m_autoScrollCondition
->Get(INFO::DEFAULT_CONTEXT
))
133 if (m_lastRenderTime
)
134 m_autoScrollDelayTime
+= currentTime
- m_lastRenderTime
;
135 if (m_autoScrollDelayTime
> (unsigned int)m_autoScrollDelay
&& m_scrollSpeed
== 0)
136 { // delay is finished - start scrolling
138 if (m_offset
< (int)m_lines
.size() - m_itemsPerPage
)
139 ScrollToOffset(m_offset
+ 1, true);
141 { // at the end, run a delay and restart
142 if (m_autoScrollRepeatAnim
)
144 if (m_autoScrollRepeatAnim
->GetState() == ANIM_STATE_NONE
)
145 m_autoScrollRepeatAnim
->QueueAnimation(ANIM_PROCESS_NORMAL
);
146 else if (m_autoScrollRepeatAnim
->GetState() == ANIM_STATE_APPLIED
)
147 { // reset to the start of the list and start the scrolling again
150 ResetAutoScrolling();
156 else if (m_autoScrollCondition
)
157 ResetAutoScrolling(); // conditional is false, so reset the autoscrolling
160 // render the repeat anim as appropriate
161 if (m_autoScrollRepeatAnim
)
163 if (m_autoScrollRepeatAnim
->GetProcess() != ANIM_PROCESS_NONE
)
165 m_autoScrollRepeatAnim
->Animate(currentTime
, true);
166 TransformMatrix matrix
;
167 m_autoScrollRepeatAnim
->RenderAnimation(matrix
);
168 m_cachedTextMatrix
= CServiceBroker::GetWinSystem()->GetGfxContext().AddTransform(matrix
);
171 // update our scroll position as necessary
172 if (m_scrollSpeed
!= 0)
175 if (m_lastRenderTime
)
176 m_scrollOffset
+= m_scrollSpeed
* (currentTime
- m_lastRenderTime
);
177 if ((m_scrollSpeed
< 0 && m_scrollOffset
< m_offset
* m_itemHeight
) ||
178 (m_scrollSpeed
> 0 && m_scrollOffset
> m_offset
* m_itemHeight
))
180 m_scrollOffset
= m_offset
* m_itemHeight
;
183 m_lastRenderTime
= currentTime
;
187 CGUIMessage
msg(GUI_MSG_ITEM_SELECT
, GetID(), m_pageControl
,
188 MathUtils::round_int(static_cast<double>(m_scrollOffset
/ m_itemHeight
)));
189 SendWindowMessage(msg
);
192 CGUIControl::Process(currentTime
, dirtyregions
);
194 if (m_autoScrollRepeatAnim
)
195 CServiceBroker::GetWinSystem()->GetGfxContext().RemoveTransform();
198 void CGUITextBox::Render()
200 // render the repeat anim as appropriate
201 if (m_autoScrollRepeatAnim
)
202 CServiceBroker::GetWinSystem()->GetGfxContext().SetTransform(m_cachedTextMatrix
);
204 if (CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_posX
, m_posY
, m_width
, m_renderHeight
))
206 // we offset our draw position to take into account scrolling and whether or not our focused
207 // item is offscreen "above" the list.
208 int offset
= (int)(m_scrollOffset
/ m_itemHeight
);
210 float posY
= m_posY
+ offset
* m_itemHeight
- m_scrollOffset
;
212 uint32_t alignment
= m_label
.align
;
214 if (alignment
& XBFONT_CENTER_Y
)
218 float textHeight
= m_font
->GetTextHeight(std::min((unsigned int)m_lines
.size(), m_itemsPerPage
));
220 if (textHeight
<= m_renderHeight
)
221 posY
+= (m_renderHeight
- textHeight
) * 0.5f
;
224 alignment
&= ~XBFONT_CENTER_Y
;
227 // alignment correction
228 if (alignment
& XBFONT_CENTER_X
)
229 posX
+= m_width
* 0.5f
;
230 if (alignment
& XBFONT_RIGHT
)
236 int current
= offset
;
238 // set the main text color
240 m_colors
[0] = m_label
.textColor
;
242 while (posY
< m_posY
+ m_renderHeight
&& current
< (int)m_lines
.size())
244 const CGUIString
& lineString
= m_lines
[current
];
245 float linePosX
= posX
;
246 uint32_t align
= alignment
;
248 if (lineString
.m_text
.size() && lineString
.m_carriageReturn
)
249 align
&= ~XBFONT_JUSTIFIED
; // last line of a paragraph shouldn't be justified
251 if (align
& XBFONT_RIGHT
)
253 // We need to adjust the posX in similar way the CGUILabel recalculate the render rect
254 // see CGUILabel::UpdateRenderRect()
255 linePosX
-= GetTextWidth(lineString
.m_text
);
258 m_font
->DrawText(linePosX
, posY
, m_colors
, m_label
.shadowColor
, lineString
.m_text
, align
,
260 posY
+= m_itemHeight
;
266 CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
268 if (m_autoScrollRepeatAnim
)
269 CServiceBroker::GetWinSystem()->GetGfxContext().RemoveTransform();
270 CGUIControl::Render();
273 bool CGUITextBox::OnMessage(CGUIMessage
& message
)
275 if (message
.GetControlId() == GetID())
277 if (message
.GetMessage() == GUI_MSG_LABEL_SET
)
281 ResetAutoScrolling();
282 CGUITextLayout::Reset();
283 m_info
.SetLabel(message
.GetLabel(), "", GetParentID());
286 if (message
.GetMessage() == GUI_MSG_LABEL_RESET
)
290 ResetAutoScrolling();
291 CGUITextLayout::Reset();
296 if (message
.GetMessage() == GUI_MSG_PAGE_CHANGE
)
298 if (message
.GetSenderId() == m_pageControl
)
300 Scroll(message
.GetParam1());
305 if (message
.GetMessage() == GUI_MSG_SET_TYPE
)
307 UseMonoFont(message
.GetParam1() == 1 ? true : false);
312 return CGUIControl::OnMessage(message
);
315 float CGUITextBox::GetHeight() const
317 return m_renderHeight
;
320 void CGUITextBox::SetMinHeight(float minHeight
)
322 if (m_minHeight
!= minHeight
)
325 m_minHeight
= minHeight
;
328 void CGUITextBox::UpdatePageControl()
332 CGUIMessage
msg(GUI_MSG_LABEL_RESET
, GetID(), m_pageControl
, m_itemsPerPage
, m_lines
.size());
333 SendWindowMessage(msg
);
337 bool CGUITextBox::CanFocus() const
342 void CGUITextBox::SetPageControl(int pageControl
)
344 m_pageControl
= pageControl
;
347 void CGUITextBox::SetInfo(const GUIINFO::CGUIInfoLabel
&infoLabel
)
352 void CGUITextBox::Scroll(unsigned int offset
)
354 ResetAutoScrolling();
355 if (m_lines
.size() <= m_itemsPerPage
)
356 return; // no need to scroll
357 if (offset
> m_lines
.size() - m_itemsPerPage
)
358 offset
= m_lines
.size() - m_itemsPerPage
; // on last page
359 ScrollToOffset(offset
);
362 void CGUITextBox::ScrollToOffset(int offset
, bool autoScroll
)
364 m_scrollOffset
= m_offset
* m_itemHeight
;
365 int timeToScroll
= autoScroll
? m_autoScrollTime
: m_scrollTime
;
366 m_scrollSpeed
= (offset
* m_itemHeight
- m_scrollOffset
) / timeToScroll
;
370 void CGUITextBox::SetAutoScrolling(const TiXmlNode
*node
)
373 const TiXmlElement
*scroll
= node
->FirstChildElement("autoscroll");
376 scroll
->Attribute("delay", &m_autoScrollDelay
);
377 scroll
->Attribute("time", &m_autoScrollTime
);
378 if (scroll
->FirstChild())
379 m_autoScrollCondition
= CServiceBroker::GetGUI()->GetInfoManager().Register(scroll
->FirstChild()->ValueStr(), GetParentID());
381 if (scroll
->Attribute("repeat", &repeatTime
))
382 m_autoScrollRepeatAnim
= new CAnimation(CAnimation::CreateFader(100, 0, repeatTime
, 1000));
386 void CGUITextBox::SetAutoScrolling(int delay
, int time
, int repeatTime
, const std::string
&condition
/* = "" */)
388 m_autoScrollDelay
= delay
;
389 m_autoScrollTime
= time
;
390 if (!condition
.empty())
391 m_autoScrollCondition
= CServiceBroker::GetGUI()->GetInfoManager().Register(condition
, GetParentID());
392 m_autoScrollRepeatAnim
= new CAnimation(CAnimation::CreateFader(100, 0, repeatTime
, 1000));
395 void CGUITextBox::ResetAutoScrolling()
397 m_autoScrollDelayTime
= 0;
398 if (m_autoScrollRepeatAnim
)
399 m_autoScrollRepeatAnim
->ResetAnimation();
402 unsigned int CGUITextBox::GetRows() const
404 return m_lines
.size();
407 int CGUITextBox::GetNumPages() const
409 return m_itemsPerPage
> 0 ? (GetRows() + m_itemsPerPage
- 1) / m_itemsPerPage
: 0;
412 int CGUITextBox::GetCurrentPage() const
414 if (m_offset
+ m_itemsPerPage
>= GetRows()) // last page
415 return GetNumPages();
416 return m_offset
/ m_itemsPerPage
+ 1;
419 std::string
CGUITextBox::GetLabel(int info
) const
424 case CONTAINER_NUM_PAGES
:
425 label
= std::to_string(GetNumPages());
427 case CONTAINER_CURRENT_PAGE
:
428 label
= std::to_string(GetCurrentPage());
436 bool CGUITextBox::GetCondition(int condition
, int data
) const
440 case CONTAINER_HAS_NEXT
:
441 return (GetCurrentPage() < GetNumPages());
442 case CONTAINER_HAS_PREVIOUS
:
443 return (GetCurrentPage() > 1);
449 std::string
CGUITextBox::GetDescription() const
454 void CGUITextBox::UpdateVisibility(const CGUIListItem
*item
)
456 // we have to update the page control when we become visible
457 // as another control may be sharing the same page control when we're
459 bool wasVisible
= IsVisible();
460 CGUIControl::UpdateVisibility(item
);
461 if (IsVisible() && !wasVisible
)