1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
5  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
6  *
7  * Distributed under the terms of the ISC license; see accompanying file
8  * "COPYING" for details.
9  *
10  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11  * See accompanying file "TRADEMARK" for details.
12  *
13  * To redistribute this file separately, substitute the full license texts
14  * for the above references.
15  */
16 // generic user interface
17 // tab control
18 
19 #include "C4Include.h"
20 #include "gui/C4Gui.h"
21 
22 #include "graphics/C4Draw.h"
23 #include "graphics/C4FacetEx.h"
24 #include "graphics/C4GraphicsResource.h"
25 #include "gui/C4MouseControl.h"
26 
27 namespace C4GUI
28 {
29 
30 
31 // ----------------------------------------------------
32 // Tabular::Sheet
33 
Sheet(const char * szTitle,const C4Rect & rcBounds,int32_t icoTitle,bool fHasCloseButton,bool fTitleMarkup)34 	Tabular::Sheet::Sheet(const char *szTitle, const C4Rect &rcBounds, int32_t icoTitle, bool fHasCloseButton, bool fTitleMarkup)
35 			: Window(), icoTitle(icoTitle), cHotkey(0), dwCaptionClr(0u), fHasCloseButton(fHasCloseButton), fCloseButtonHighlighted(false), fTitleMarkup(fTitleMarkup)
36 	{
37 		// store title
38 		if (szTitle)
39 		{
40 			sTitle.Copy(szTitle);
41 			if (fTitleMarkup) ExpandHotkeyMarkup(sTitle, cHotkey);
42 		}
43 		// set bounds
44 		SetBounds(rcBounds);
45 	}
46 
DrawCaption(C4TargetFacet & cgo,int32_t x,int32_t y,int32_t iMaxWdt,bool fLarge,bool fActive,bool fFocus,C4Facet * pfctClip,C4Facet * pfctIcon,CStdFont * pUseFont)47 	void Tabular::Sheet::DrawCaption(C4TargetFacet &cgo, int32_t x, int32_t y, int32_t iMaxWdt, bool fLarge, bool fActive, bool fFocus, C4Facet *pfctClip, C4Facet *pfctIcon, CStdFont *pUseFont)
48 	{
49 		// calculations
50 		int32_t iTxtHgt, iTxtWdt;
51 		GetCaptionSize(&iTxtWdt, &iTxtHgt, fLarge, fActive, pfctClip, pfctIcon, pUseFont);
52 		if (pfctClip) iMaxWdt = iTxtWdt;
53 		CStdFont &rUseFont = pUseFont ? *pUseFont : (fLarge ? ::GraphicsResource.CaptionFont : ::GraphicsResource.TextFont);
54 		if (pfctClip && pfctIcon)
55 		{
56 			// tab with clip gfx: Icon on top of text
57 			// x and y mark topleft pos; iTxtWdt and iTxtHgt mark overall size
58 			pfctClip->Draw(cgo.Surface, x, y);
59 			int32_t xCenter = x + iTxtWdt/2, yCenter = y + iTxtHgt/2;
60 			int32_t iLabelHgt = rUseFont.GetLineHeight(); int32_t iIconLabelSpacing = 2;
61 			int32_t yTop = yCenter - (pfctIcon->Hgt+iIconLabelSpacing+iLabelHgt)/2;
62 			pfctIcon->Draw(cgo.Surface, xCenter-pfctIcon->Wdt/2, yTop, icoTitle);
63 			pDraw->TextOut(sTitle.getData(), rUseFont, 1.0f, cgo.Surface, xCenter, yTop + pfctIcon->Hgt+iIconLabelSpacing, fActive ? C4GUI_GfxTabCaptActiveClr : C4GUI_GfxTabCaptInactiveClr , ACenter);
64 		}
65 		// focus highlight
66 		if (fFocus)
67 		{
68 			pDraw->SetBlitMode(C4GFXBLIT_ADDITIVE);
69 			::GraphicsResource.fctButtonHighlightRound.DrawX(cgo.Surface, (fLarge ? x : x - iTxtWdt/2)+5, y+3, (fLarge ? iMaxWdt : iTxtWdt)-10, iTxtHgt-6);
70 			pDraw->ResetBlitMode();
71 		}
72 		if (!(pfctClip && pfctIcon))
73 		{
74 			// classical tab without clip
75 			// icon
76 			int32_t xo = x;
77 			if (icoTitle>=0)
78 			{
79 				C4Facet cgoIcon(cgo.Surface, x, y+1, iTxtHgt-2, iTxtHgt-2);
80 				if (fLarge)
81 				{
82 					// large caption: x parameter denotes left pos of icon
83 					x += iTxtHgt + 2;
84 				}
85 				else
86 				{
87 					// small caption: x parameter denotes drawing center
88 					// note that iTxtWdt includes the icon (and close button) as well
89 					cgoIcon.X -= iTxtWdt / 2;
90 					x += iTxtHgt / 2;
91 				}
92 				Icon::GetIconFacet((Icons)icoTitle).Draw(cgoIcon);
93 			}
94 			// text
95 			if (!fLarge && fHasCloseButton) x -= iTxtHgt/2;
96 			uint32_t dwClr = dwCaptionClr;
97 			if (!dwClr) dwClr = fActive ? C4GUI_CaptionFontClr : C4GUI_InactCaptionFontClr;
98 			pDraw->TextOut(sTitle.getData(), rUseFont, fLarge ? 1.2f : 1.0f, cgo.Surface, x, y, dwClr, fLarge ? ALeft : ACenter, fTitleMarkup);
99 			// close button
100 			if (fHasCloseButton)
101 			{
102 				xo += iTxtWdt / (2 - fLarge) - iTxtHgt + 1;
103 				C4Facet cgoCloseBtn(cgo.Surface, xo, y+1, iTxtHgt-2, iTxtHgt-2);
104 				if (!fCloseButtonHighlighted) pDraw->ActivateBlitModulation(0x7f7f7f);
105 				Icon::GetIconFacet(Ico_Close).Draw(cgoCloseBtn);
106 				if (!fCloseButtonHighlighted) pDraw->DeactivateBlitModulation();
107 			}
108 		}
109 	}
110 
GetCaptionSize(int32_t * piWdt,int32_t * piHgt,bool fLarge,bool fActive,C4Facet * pfctClip,C4Facet * pfctIcon,CStdFont * pUseFont)111 	void Tabular::Sheet::GetCaptionSize(int32_t *piWdt, int32_t *piHgt, bool fLarge, bool fActive, C4Facet *pfctClip, C4Facet *pfctIcon, CStdFont *pUseFont)
112 	{
113 		// caption by gfx?
114 		if (pfctClip && pfctIcon)
115 		{
116 			if (piWdt) *piWdt = Tabular::GetLeftClipSize(pfctClip);
117 			if (piHgt) *piHgt = pfctClip->Hgt;
118 			return;
119 		}
120 		// caption by text
121 		int32_t iWdt, iHgt;
122 		CStdFont &rUseFont = pUseFont ? *pUseFont : (fLarge ? ::GraphicsResource.CaptionFont : ::GraphicsResource.TextFont);
123 		if (!rUseFont.GetTextExtent(sTitle.getData(), iWdt, iHgt, fTitleMarkup))
124 		{
125 			iWdt=70; iHgt=rUseFont.GetLineHeight();
126 		}
127 		if (fLarge) { iWdt = iWdt * 6 / 5; iHgt = iHgt * 6 / 5; }
128 		// add icon width
129 		if (icoTitle>=0) iWdt += iHgt + fLarge*2;
130 		// add close button width
131 		if (fHasCloseButton) iWdt += iHgt + fLarge*2;
132 		// assign output vars
133 		if (piWdt) *piWdt = iWdt;
134 		if (piHgt) *piHgt = iHgt;
135 	}
136 
IsPosOnCloseButton(int32_t x,int32_t y,int32_t iCaptWdt,int32_t iCaptHgt,bool fLarge)137 	bool Tabular::Sheet::IsPosOnCloseButton(int32_t x, int32_t y, int32_t iCaptWdt, int32_t iCaptHgt, bool fLarge)
138 	{
139 		// close button is on right end of tab
140 		return fHasCloseButton && Inside<int32_t>(x, iCaptWdt-iCaptHgt+1, iCaptWdt-2) && Inside<int32_t>(y, 1, iCaptHgt-2);
141 	}
142 
SetTitle(const char * szNewTitle)143 	void Tabular::Sheet::SetTitle(const char *szNewTitle)
144 	{
145 		if (sTitle == szNewTitle) return;
146 		if (szNewTitle)
147 		{
148 			sTitle.Copy(szNewTitle);
149 			if (fTitleMarkup) ExpandHotkeyMarkup(sTitle, cHotkey);
150 		}
151 		else
152 		{
153 			sTitle.Clear();
154 			cHotkey = '\0';
155 		}
156 		Tabular *pTabular = static_cast<Tabular *>(GetParent());
157 		if (pTabular) pTabular->SheetsChanged();
158 	}
159 
IsActiveSheet()160 	bool Tabular::Sheet::IsActiveSheet()
161 	{
162 		Tabular *pTabular = static_cast<Tabular *>(GetParent());
163 		if (pTabular) return pTabular->GetActiveSheet() == this;
164 		return false;
165 	}
166 
167 // ----------------------------------------------------
168 // Tabular
169 
Tabular(C4Rect & rtBounds,TabPosition eTabPos)170 	Tabular::Tabular(C4Rect &rtBounds, TabPosition eTabPos) : Control(rtBounds), pActiveSheet(nullptr), eTabPos(eTabPos), iMaxTabWidth(0),
171 			iCaptionLengthTotal(0), iCaptionScrollPos(0), fScrollingLeft(false), fScrollingRight(false), fScrollingLeftDown(false),
172 			fScrollingRightDown(false), iSheetMargin(4), fDrawSelf(true), pfctBack(nullptr), pfctClip(nullptr), pfctIcons(nullptr), pSheetCaptionFont(nullptr)
173 	{
174 		// calc client rect
175 		UpdateOwnPos();
176 		// key bindings for tab selection, if this is not an invisible "blind" tabular
177 		if (eTabPos != tbNone)
178 		{
179 			// Ctrl+(Shift-)Tab works with dialog focus only (assumes max one tabular per dialog)
180 			// Arrow keys work if control is focused only
181 			C4CustomKey::CodeList Keys;
182 			Keys.emplace_back(K_UP);
183 			if (Config.Controls.GamepadGuiControl)
184 			{
185 				ControllerKeys::Up(Keys);
186 			}
187 			pKeySelUp = new C4KeyBinding(Keys, "GUITabularSelUp", KEYSCOPE_Gui,
188 			                             new ControlKeyCB<Tabular>(*this, &Tabular::KeySelUp), C4CustomKey::PRIO_Ctrl);
189 
190 			Keys.clear();
191 			Keys.emplace_back(K_DOWN);
192 			if (Config.Controls.GamepadGuiControl)
193 			{
194 				ControllerKeys::Down(Keys);
195 			}
196 			pKeySelDown = new C4KeyBinding(Keys, "GUITabularSelDown", KEYSCOPE_Gui,
197 			                               new ControlKeyCB<Tabular>(*this, &Tabular::KeySelDown), C4CustomKey::PRIO_Ctrl);
198 
199 			pKeySelUp2 = new C4KeyBinding(C4KeyCodeEx(K_TAB, C4KeyShiftState(KEYS_Shift | KEYS_Control)), "GUITabularSelUp2", KEYSCOPE_Gui,
200 			                              new DlgKeyCB<Tabular>(*this, &Tabular::KeySelUp), C4CustomKey::PRIO_Ctrl);
201 			pKeySelDown2 = new C4KeyBinding(C4KeyCodeEx(K_TAB, KEYS_Control), "GUITabularSelDown2", KEYSCOPE_Gui,
202 			                                new DlgKeyCB<Tabular>(*this, &Tabular::KeySelDown), C4CustomKey::PRIO_Ctrl);
203 			pKeyCloseTab = new C4KeyBinding(C4KeyCodeEx(K_F4, KEYS_Control), "GUITabularCloseTab", KEYSCOPE_Gui,
204 			                                new DlgKeyCB<Tabular>(*this, &Tabular::KeyCloseTab), C4CustomKey::PRIO_Ctrl);
205 		}
206 		else
207 		{
208 			pKeySelUp = pKeySelDown = pKeySelUp2 = pKeySelDown2 = pKeyCloseTab = nullptr;
209 		}
210 		SheetsChanged();
211 	}
212 
~Tabular()213 	Tabular::~Tabular()
214 	{
215 		if (pKeyCloseTab) delete pKeyCloseTab;
216 		if (pKeySelDown2) delete pKeySelDown2;
217 		if (pKeySelUp2) delete pKeySelUp2;
218 		if (pKeySelDown) delete pKeySelDown;
219 		if (pKeySelUp) delete pKeySelUp;
220 	}
221 
KeySelUp()222 	bool Tabular::KeySelUp()
223 	{
224 		// keyboard callback: Select previous sheet
225 		int32_t iNewSel = GetActiveSheetIndex() - 1;
226 		if (iNewSel < 0) iNewSel = GetSheetCount() - 1;
227 		if (iNewSel < 0) return false;
228 		SelectSheet(iNewSel, true);
229 		return true;
230 	}
231 
KeySelDown()232 	bool Tabular::KeySelDown()
233 	{
234 		// keyboard callback: Select next sheet
235 		int32_t iNewSel = GetActiveSheetIndex() + 1, iSheetCount = GetSheetCount();
236 		if (iNewSel >= iSheetCount)
237 		{
238 			if (!iSheetCount) return false;
239 			else iNewSel = 0;
240 		}
241 		SelectSheet(iNewSel, true);
242 		return true;
243 	}
244 
KeyCloseTab()245 	bool Tabular::KeyCloseTab()
246 	{
247 		// keyboard callback: Close currnet sheet
248 		// only for sheets that can be closed
249 		Sheet *pCurrentSheet = GetActiveSheet();
250 		if (!pCurrentSheet) return false;
251 		if (!pCurrentSheet->HasCloseButton()) return false;
252 		pCurrentSheet->UserClose();
253 		return true;
254 	}
255 
SelectionChanged(bool fByUser)256 	void Tabular::SelectionChanged(bool fByUser)
257 	{
258 		Control *pFocusCtrl = nullptr;
259 		Dialog *pDlg = GetDlg();
260 		if (pDlg) pFocusCtrl = pDlg->GetFocus();
261 		// any selection?
262 		if (pActiveSheet)
263 		{
264 			// effect
265 			if (fByUser) GUISound("UI::Select");
266 			// update in sheet
267 			pActiveSheet->OnShown(fByUser);
268 		}
269 		// make only active sheet visible
270 		for (Element *pSheet = GetFirst(); pSheet; pSheet = pSheet->GetNext())
271 		{
272 			pSheet->SetVisibility(pSheet == pActiveSheet);
273 		}
274 		// if nothing is selected now, but something was selected before, focus new default control
275 		if (pFocusCtrl && !pDlg->GetFocus()) pDlg->SetFocus(pDlg->GetDefaultControl(), fByUser);
276 	}
277 
SheetsChanged()278 	void Tabular::SheetsChanged()
279 	{
280 		Sheet *pSheet;
281 		if (eTabPos)
282 		{
283 			// update iMaxTabWidth by new set of sheet labels
284 			iSheetOff = 20;
285 			iMaxTabWidth = 20;
286 			iSheetSpacing = (eTabPos == tbLeft) ? -10 : 20;
287 			int32_t iSheetNum=0, iTotalHgt=iSheetOff;
288 			iCaptionLengthTotal = iSheetOff;
289 			for (pSheet = (Sheet *) GetFirst(); pSheet; pSheet = (Sheet *) pSheet->GetNext())
290 			{
291 				int32_t iTabWidth, iTabHeight;
292 				pSheet->GetCaptionSize(&iTabWidth, &iTabHeight, HasLargeCaptions(), pSheet == pActiveSheet, pfctClip, pfctIcons, pSheetCaptionFont);
293 				iTabWidth += (eTabPos == tbLeft) ? 20 : iSheetSpacing;
294 				iMaxTabWidth = std::max(iTabWidth, iMaxTabWidth);
295 				if (eTabPos == tbLeft)
296 				{
297 					iTotalHgt += iTabHeight;
298 					if (iSheetNum++) iTotalHgt += iSheetSpacing;
299 				}
300 				else
301 				{
302 					iCaptionLengthTotal += iTabWidth;
303 				}
304 			}
305 			// update sheet positioning
306 			if (eTabPos == tbLeft && iTotalHgt > rcBounds.Hgt-GetMarginBottom())
307 			{
308 				// sheet captions dont fit - condense them
309 				iSheetSpacing -= (iTotalHgt-rcBounds.Hgt+GetMarginBottom()-iSheetOff) / iSheetNum;
310 				iSheetOff = 0;
311 			}
312 			else if (eTabPos == tbTop)
313 			{
314 			}
315 		}
316 		// update all sheet sizes
317 		UpdateSize();
318 		// update scrolling range/status
319 		UpdateScrolling();
320 	}
321 
UpdateScrolling()322 	void Tabular::UpdateScrolling()
323 	{
324 		// any scrolling necessary?
325 		int32_t iAvailableTabSpace = rcBounds.Wdt;
326 		int32_t iScrollPinSize = GetTopSize();
327 		if (eTabPos != tbTop || iCaptionLengthTotal <= iAvailableTabSpace || iAvailableTabSpace <= iScrollPinSize*2)
328 		{
329 			fScrollingLeft = fScrollingRight = fScrollingLeftDown = fScrollingRightDown = false;
330 			iCaptionScrollPos = 0;
331 		}
332 		else
333 		{
334 			// must scroll; update scrolling parameters
335 			fScrollingLeft = !!iCaptionScrollPos;
336 			if (!fScrollingLeft)
337 			{
338 				fScrollingRight = true;
339 				fScrollingLeftDown = false;
340 			}
341 			else
342 			{
343 				iAvailableTabSpace -= iScrollPinSize;
344 				fScrollingRight = (iCaptionLengthTotal - iCaptionScrollPos > iAvailableTabSpace);
345 				// do not scroll past right end
346 				if (!fScrollingRight)
347 				{
348 					iCaptionScrollPos = iCaptionLengthTotal - iAvailableTabSpace;
349 					fScrollingRightDown = false;
350 				}
351 			}
352 		}
353 	}
354 
DoCaptionScroll(int32_t iDir)355 	void Tabular::DoCaptionScroll(int32_t iDir)
356 	{
357 		// store time of scrolling change
358 		tLastScrollTime = C4TimeMilliseconds::Now();
359 		// change scrolling within max range
360 		int32_t iAvailableTabSpace = rcBounds.Wdt;
361 		int32_t iScrollPinSize = GetTopSize();
362 		iCaptionScrollPos = Clamp<int32_t>(iCaptionScrollPos + iDir*iAvailableTabSpace/2, 0, iCaptionLengthTotal - iAvailableTabSpace + iScrollPinSize);
363 		UpdateScrolling();
364 	}
365 
DrawElement(C4TargetFacet & cgo)366 	void Tabular::DrawElement(C4TargetFacet &cgo)
367 	{
368 		if (!fDrawSelf) return;
369 		bool fGfx = HasGfx();
370 		// execute scrolling
371 		bool fCaptionScrollDelayOver = C4TimeMilliseconds::Now() - tLastScrollTime >= C4GUI_TabCaptionScrollTime;
372 		if ((fScrollingLeftDown || fScrollingRightDown) && fCaptionScrollDelayOver)
373 			DoCaptionScroll(fScrollingRightDown - fScrollingLeftDown);
374 		// border
375 		if (!fGfx) Draw3DFrame(cgo, false, 1, 0xaf, eTabPos!=tbTop, GetTopSize(), eTabPos!=tbLeft, GetLeftSize());
376 		// calc positions
377 		int32_t x0 = cgo.TargetX + rcBounds.x + GetLeftSize(),
378 		        y0 = cgo.TargetY + rcBounds.y + GetTopSize(),
379 		        x1 = cgo.TargetX + rcBounds.x + rcBounds.Wdt - 1,
380 		        y1 = cgo.TargetY + rcBounds.y + rcBounds.Hgt - 1;
381 		// main area BG
382 		if (!fGfx) pDraw->DrawBoxDw(cgo.Surface, x0,y0,x1,y1, C4GUI_StandardBGColor);
383 		// no tabs?
384 		if (!eTabPos)
385 		{
386 			if (fGfx)
387 				pfctBack->DrawX(cgo.Surface, x0, y0, x1-x0+1, y1-y0+1);
388 			return;
389 		}
390 		bool fLeft = (eTabPos == tbLeft);
391 		// top or left bar
392 		int32_t d=(fLeft ? y0 : x0)+iSheetOff; // current tab position (leave some space to the left/top)
393 		int32_t ad0=0,ad1=0, aCptTxX=0, aCptTxY=0;
394 		// scrolling in captions
395 		int32_t iScrollSize = GetTopSize();
396 		if (fScrollingLeft) d -= iCaptionScrollPos + iScrollSize;
397 		// tabs
398 		for (Sheet *pSheet = (Sheet *) GetFirst(); pSheet; pSheet = (Sheet *) pSheet->GetNext())
399 		{
400 			// get tab size
401 			int32_t iTabWidth, iTabHeight;
402 			pSheet->GetCaptionSize(&iTabWidth, &iTabHeight, HasLargeCaptions(), pSheet == pActiveSheet, pfctClip, pfctIcons, pSheetCaptionFont);
403 			// leave some space around caption
404 			iTabWidth += fLeft ? 20 : iSheetSpacing;
405 			iTabHeight += fLeft ? iSheetSpacing : 10;
406 			// draw caption bg
407 			if (!fGfx)
408 			{
409 				float vtx[8];
410 				if (fLeft)
411 				{
412 					vtx[0] = x0; vtx[1] = d;
413 					vtx[2] = x0-GetLeftSize(); vtx[3] = d;
414 					vtx[4] = x0-GetLeftSize(); vtx[5] = d+iTabHeight;
415 					vtx[6] = x0; vtx[7] = d+iTabHeight;
416 				}
417 				else
418 				{
419 					vtx[0] = d+1; vtx[1] = y0;
420 					vtx[2] = d+4+1; vtx[3] = y0-GetTopSize();
421 					vtx[4] = d+iTabWidth-4; vtx[5] = y0-GetTopSize();
422 					vtx[6] = d+iTabWidth; vtx[7] = y0;
423 				}
424 				DWORD dwClr = (pSheet == pActiveSheet) ? C4GUI_ActiveTabBGColor : C4GUI_StandardBGColor;
425 				pDraw->DrawQuadDw(cgo.Surface, vtx, dwClr, dwClr, dwClr, dwClr, nullptr);
426 				// draw caption frame
427 				// TODO: Switch to PerformMultiLines
428 				pDraw->DrawLineDw(cgo.Surface, (float)vtx[0]-1     , (float)vtx[1]      , (float)vtx[2]-1    ,(float)vtx[3]        , C4GUI_BorderColorA1);
429 				pDraw->DrawLineDw(cgo.Surface, (float)vtx[2]-1     , (float)vtx[3]      , (float)vtx[4]-fLeft,(float)vtx[5]        , C4GUI_BorderColorA1);
430 				pDraw->DrawLineDw(cgo.Surface, (float)vtx[4]       , (float)vtx[5]      , (float)vtx[6]      ,(float)vtx[7]        , C4GUI_BorderColorA1);
431 				pDraw->DrawLineDw(cgo.Surface, (float)vtx[0]       , (float)vtx[1]+fLeft, (float)vtx[2]      ,(float)vtx[3]+fLeft  , C4GUI_BorderColorA2);
432 				pDraw->DrawLineDw(cgo.Surface, (float)vtx[2]-!fLeft, (float)vtx[3]+1    , (float)vtx[4]      ,(float)vtx[5]+!fLeft , C4GUI_BorderColorA2);
433 				pDraw->DrawLineDw(cgo.Surface, (float)vtx[4]+1     , (float)vtx[5]+fLeft, (float)vtx[6]      ,(float)vtx[7]+fLeft  , C4GUI_BorderColorA2);
434 			}
435 			// draw caption text
436 			int32_t iCptTextX = fLeft ? (x0-GetLeftSize()+10) : (d+iTabWidth/2);
437 			int32_t iCptTextY = fLeft ? (d+iSheetSpacing/2) : (y0-GetTopSize()+2);
438 			if (pSheet == pActiveSheet)
439 			{
440 				// store active sheet pos for border line or later drawing
441 				ad0=d; ad1=d+(fLeft ? iTabHeight : iTabWidth);
442 				aCptTxX = iCptTextX; aCptTxY = iCptTextY;
443 				// draw active caption
444 				if (!fGfx) pSheet->DrawCaption(cgo, iCptTextX, iCptTextY, iMaxTabWidth, fLeft, true, HasDrawFocus(), nullptr, nullptr, nullptr);
445 			}
446 			else
447 			{
448 				// draw inactive caption
449 				pSheet->DrawCaption(cgo, iCptTextX, iCptTextY, iMaxTabWidth, fLeft, false, false, pfctClip, pfctIcons, pSheetCaptionFont);
450 			}
451 			// advance position
452 			d += (fLeft ? iTabHeight : iTabWidth)+2;
453 		}
454 		// draw tab border line across everything but active tab
455 		if (!fGfx) if (ad0||ad1)
456 			{
457 				pDraw->DrawLineDw(cgo.Surface, (float)x0  ,(float)y0  ,(float)(fLeft ? x0 : ad0), (float)(fLeft ? ad0 : y0), C4GUI_BorderColorA1);
458 				pDraw->DrawLineDw(cgo.Surface, (float)(x0+1),(float)(y0+1),(float)((fLeft ? x0 : ad0)+1), (float)((fLeft ? ad0 : y0)+1) , C4GUI_BorderColorA2);
459 				pDraw->DrawLineDw(cgo.Surface, (float)(fLeft ? x0 : ad1), (float)(fLeft ? ad1 : y0), (float)(fLeft ? x0 : x1), (float)(fLeft ? y1 : y0), C4GUI_BorderColorA1);
460 				pDraw->DrawLineDw(cgo.Surface, (float)((fLeft ? x0 : ad1)+1), (float)((fLeft ? ad1 : y0)+1), (float)((fLeft ? x0 : x1)+1), (float)((fLeft ? y1 : y0)+1), C4GUI_BorderColorA2);
461 			}
462 		// main area bg in gfx: Atop inactive tabs
463 		if (fGfx)
464 		{
465 			pfctBack->DrawX(cgo.Surface, x0, y0, x1-x0+1, y1-y0+1);
466 			// and active tab on top of that
467 			if (pActiveSheet)
468 				pActiveSheet->DrawCaption(cgo, aCptTxX, aCptTxY, iMaxTabWidth, fLeft, true, HasDrawFocus(), pfctClip, pfctIcons, pSheetCaptionFont);
469 		}
470 		// scrolling
471 		if (fScrollingLeft) ::GraphicsResource.fctBigArrows.DrawX(cgo.Surface, x0+iSheetOff,y0-iScrollSize, iScrollSize,iScrollSize, fScrollingLeftDown*2);
472 		if (fScrollingRight) ::GraphicsResource.fctBigArrows.DrawX(cgo.Surface, x1-iScrollSize,y0-iScrollSize, iScrollSize,iScrollSize, 1+fScrollingRightDown*2);
473 	}
474 
MouseInput(CMouse & rMouse,int32_t iButton,int32_t iX,int32_t iY,DWORD dwKeyParam)475 	void Tabular::MouseInput(CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam)
476 	{
477 		// tabular contains controls?
478 		if (eTabPos)
479 		{
480 			bool fLeft = (eTabPos == tbLeft);
481 			bool fInCaptionArea = ((!fLeft && Inside<int32_t>(iY, 0, GetTopSize())) || (fLeft && Inside<int32_t>(iX, 0, GetLeftSize())));
482 			if (!fInCaptionArea || iButton == C4MC_Button_LeftUp)
483 			{
484 				MouseLeaveCaptionArea();
485 			}
486 			// then check for mousedown in coloumn area
487 			else if ((iButton == C4MC_Button_LeftDown || iButton == C4MC_Button_None) && fInCaptionArea)
488 			{
489 				int32_t d=iSheetOff;
490 				// check inside scrolling buttons
491 				bool fProcessed = false;
492 				if (fScrollingLeft || fScrollingRight)
493 				{
494 					int32_t iScrollSize = GetTopSize();
495 					if (iButton == C4MC_Button_LeftDown && fScrollingRight && Inside(iX, rcBounds.Wdt-iScrollSize,rcBounds.Wdt))
496 					{
497 						fProcessed = fScrollingRightDown = true;
498 						GUISound("UI::Select");
499 						DoCaptionScroll(+1);
500 					}
501 					else if (fScrollingLeft)
502 					{
503 						if (iButton == C4MC_Button_LeftDown && Inside(iX, d,d+iScrollSize))
504 						{
505 							fProcessed = fScrollingLeftDown = true;
506 							GUISound("UI::Select");
507 							DoCaptionScroll(-1);
508 						}
509 						d -= iCaptionScrollPos + iScrollSize;
510 					}
511 				}
512 				// check on sheet captions
513 				if (!fProcessed) for (Sheet *pSheet = (Sheet *) GetFirst(); pSheet; pSheet = (Sheet *) pSheet->GetNext())
514 					{
515 						// default: Mouse not on close button
516 						pSheet->SetCloseButtonHighlight(false);
517 						// get tab width
518 						int32_t iCaptWidth,iCaptHeight,iTabWidth,iTabHeight;
519 						pSheet->GetCaptionSize(&iCaptWidth, &iCaptHeight, HasLargeCaptions(), pSheet == pActiveSheet, pfctClip, pfctIcons, pSheetCaptionFont);
520 						iTabWidth = iCaptWidth + (fLeft ? 20 : iSheetSpacing);
521 						iTabHeight = iCaptHeight + (fLeft ? iSheetSpacing : 10);
522 						// check containment in this tab (check rect only, may catch some side-clicks...)
523 						if ((!fLeft && Inside(iX, d, d+iTabWidth)) || (fLeft && Inside(iY, d, d+iTabHeight)))
524 						{
525 							// close button
526 							if (pSheet->IsPosOnCloseButton(iX-d*!fLeft-(iTabWidth-iCaptWidth)/2, iY-d*fLeft, iCaptWidth, iCaptHeight, HasLargeCaptions()))
527 							{
528 								if (iButton == C4MC_Button_LeftDown)
529 								{
530 									// Closing: Callback to sheet
531 									pSheet->UserClose();
532 								}
533 								else
534 									// just moving :Highlight
535 									pSheet->SetCloseButtonHighlight(true);
536 							}
537 							// mouse press outside close button area: Switch sheet
538 							else if (iButton == C4MC_Button_LeftDown)
539 							{
540 								if (pSheet != pActiveSheet)
541 								{
542 									pActiveSheet = pSheet;
543 									SelectionChanged(true);
544 								}
545 							}
546 							break;
547 						}
548 						// next tab
549 						d += (fLeft ? iTabHeight : iTabWidth)+2;
550 					}
551 			}
552 		}
553 		// inherited
554 		Control::MouseInput(rMouse, iButton, iX, iY, dwKeyParam);
555 	}
556 
MouseLeaveCaptionArea()557 	void Tabular::MouseLeaveCaptionArea()
558 	{
559 		// no more close buttons or scroll buttons highlighted
560 		for (Sheet *pSheet = (Sheet *) GetFirst(); pSheet; pSheet = (Sheet *) pSheet->GetNext())
561 		{
562 			// default: Mouse not on close button
563 			pSheet->SetCloseButtonHighlight(false);
564 		}
565 		if (fScrollingLeftDown || fScrollingRightDown)
566 		{
567 			// stop scrolling
568 			GUISound("UI::Select");
569 			fScrollingLeftDown = fScrollingRightDown = false;
570 		}
571 	}
572 
MouseLeave(CMouse & rMouse)573 	void Tabular::MouseLeave(CMouse &rMouse)
574 	{
575 		MouseLeaveCaptionArea();
576 		// inherited
577 		Control::MouseLeave(rMouse);
578 	}
579 
OnGetFocus(bool fByMouse)580 	void Tabular::OnGetFocus(bool fByMouse)
581 	{
582 		// inherited (tooltip)
583 		Control::OnGetFocus(fByMouse);
584 	}
585 
RemoveElement(Element * pChild)586 	void Tabular::RemoveElement(Element *pChild)
587 	{
588 		// inherited
589 		Control::RemoveElement(pChild);
590 		// clear selection var
591 		if (pChild == pActiveSheet)
592 		{
593 			// select new active sheet
594 			pActiveSheet = (Sheet *) GetFirst();
595 			SelectionChanged(false);
596 		}
597 		// update sheet labels
598 		SheetsChanged();
599 	}
600 
AddSheet(const char * szTitle,int32_t icoTitle)601 	Tabular::Sheet *Tabular::AddSheet(const char *szTitle, int32_t icoTitle)
602 	{
603 		// create new sheet in client area
604 		Sheet *pNewSheet = new Sheet(szTitle, GetContainedClientRect(), icoTitle);
605 		AddCustomSheet(pNewSheet);
606 		// k, new sheet ready!
607 		return pNewSheet;
608 	}
609 
AddCustomSheet(Sheet * pAddSheet)610 	void Tabular::AddCustomSheet(Sheet *pAddSheet)
611 	{
612 		AddElement(pAddSheet);
613 		// select it if it's first
614 		pAddSheet->SetVisibility(!pActiveSheet);
615 		if (!pActiveSheet) pActiveSheet = pAddSheet;
616 		// update sheet labels
617 		SheetsChanged();
618 	}
619 
ClearSheets()620 	void Tabular::ClearSheets()
621 	{
622 		// del all sheets
623 		Sheet *pSheet;
624 		while ((pSheet = GetSheet(0))) delete pSheet;
625 		SheetsChanged();
626 	}
627 
SelectSheet(int32_t iIndex,bool fByUser)628 	void Tabular::SelectSheet(int32_t iIndex, bool fByUser)
629 	{
630 		pActiveSheet = GetSheet(iIndex);
631 		SelectionChanged(fByUser);
632 	}
633 
SelectSheet(Sheet * pSelSheet,bool fByUser)634 	void Tabular::SelectSheet(Sheet *pSelSheet, bool fByUser)
635 	{
636 		pActiveSheet = pSelSheet;
637 		SelectionChanged(fByUser);
638 	}
639 
GetActiveSheetIndex()640 	int32_t Tabular::GetActiveSheetIndex()
641 	{
642 		int32_t i=-1;
643 		Sheet *pSheet;
644 		while ((pSheet = GetSheet(++i))) if (pSheet == pActiveSheet) return i;
645 		return -1;
646 	}
647 
SetGfx(C4Facet * pafctBack,C4Facet * pafctClip,C4Facet * pafctIcons,CStdFont * paSheetCaptionFont,bool fResizeByAspect)648 	void Tabular::SetGfx(C4Facet *pafctBack, C4Facet *pafctClip, C4Facet *pafctIcons, CStdFont *paSheetCaptionFont, bool fResizeByAspect)
649 	{
650 		// set gfx files
651 		pfctBack=pafctBack;
652 		pfctClip=pafctClip;
653 		pfctIcons=pafctIcons;
654 		pSheetCaptionFont=paSheetCaptionFont;
655 		// make sure aspect of background is used correctly
656 		if (pfctBack && fResizeByAspect)
657 		{
658 			int32_t iEffWdt = rcBounds.Wdt - GetLeftSize(), iEffHgt = rcBounds.Hgt - GetTopSize();
659 			if (iEffWdt * pfctBack->Hgt > pfctBack->Wdt * iEffHgt)
660 			{
661 				// control is too wide: center it
662 				int32_t iOversize = iEffWdt - pfctBack->Wdt * iEffHgt / pfctBack->Hgt;
663 				C4Rect rtBounds = GetBounds();
664 				rtBounds.x += iOversize/2;
665 				rtBounds.Wdt -= iOversize;
666 				SetBounds(rtBounds);
667 			}
668 			else
669 			{
670 				// control is too tall: cap at bottom
671 				int32_t iOversize = iEffHgt - pfctBack->Hgt * iEffWdt / pfctBack->Wdt;
672 				C4Rect rtBounds = GetBounds();
673 				rtBounds.y += iOversize;
674 				rtBounds.Hgt -= iOversize;
675 				SetBounds(rtBounds);
676 			}
677 		}
678 		SheetsChanged();
679 	}
680 
UpdateSize()681 	void Tabular::UpdateSize()
682 	{
683 		Control::UpdateSize();
684 		// update all sheets
685 		for (Sheet *pSheet = static_cast<Sheet *>(GetFirst()); pSheet; pSheet = static_cast<Sheet *>(pSheet->GetNext()))
686 			pSheet->SetBounds(GetContainedClientRect());
687 	}
688 
689 
690 } // end of namespace
691 
692