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