/* * OpenClonk, http://www.openclonk.org * * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/ * Copyright (c) 2009-2016, The OpenClonk Team and contributors * * Distributed under the terms of the ISC license; see accompanying file * "COPYING" for details. * * "Clonk" is a registered trademark of Matthes Bender, used with permission. * See accompanying file "TRADEMARK" for details. * * To redistribute this file separately, substitute the full license texts * for the above references. */ // generic user interface // eye candy #include "C4Include.h" #include "gui/C4Gui.h" #include "graphics/C4Draw.h" #include "graphics/C4GraphicsResource.h" #include "gui/C4MouseControl.h" namespace C4GUI { // -------------------------------------------------- // Label void Label::DrawElement(C4TargetFacet &cgo) { // print out pDraw->TextOut(sText.getData(), *pFont, 1.0f, cgo.Surface, x0 + cgo.TargetX, rcBounds.y + cgo.TargetY, dwFgClr, iAlign, fMarkup); } Label::Label(const char *szLblText, int32_t iX0, int32_t iTop, int32_t iAlign, DWORD dwFClr, CStdFont *pFont, bool fMakeReadableOnBlack, bool fMarkup) : Element(), dwFgClr(dwFClr), x0(iX0), iAlign(iAlign), pFont(pFont), cHotkey(0), fAutosize(true), fMarkup(fMarkup), pClickFocusControl(nullptr) { // make color readable if (fMakeReadableOnBlack) MakeColorReadableOnBlack(dwFgClr); // default font if (!this->pFont) this->pFont = &::GraphicsResource.TextFont; // set top rcBounds.y = iTop; // update text SetText(szLblText); } Label::Label(const char *szLblText, const C4Rect &rcBounds, int32_t iAlign, DWORD dwFClr, CStdFont *pFont, bool fMakeReadableOnBlack, bool fAutosize, bool fMarkup) : Element(), dwFgClr(dwFClr), iAlign(iAlign), pFont(pFont), cHotkey(0), fAutosize(fAutosize), fMarkup(fMarkup), pClickFocusControl(nullptr) { // make color readable if (fMakeReadableOnBlack) MakeColorReadableOnBlack(dwFgClr); this->rcBounds = rcBounds; // default font if (!this->pFont) this->pFont = &::GraphicsResource.TextFont; // set x0 UpdateOwnPos(); // update text SetText(szLblText); } void Label::MouseInput(CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam) { // left-click changes focus if (pClickFocusControl && iButton == C4MC_Button_LeftDown) GetDlg()->SetFocus(pClickFocusControl, true); // inherited Element::MouseInput(rMouse, iButton, iX, iY, dwKeyParam); } void Label::SetText(const char *szToText, bool fAllowHotkey) { // set new text if (szToText) { sText.Copy(szToText); // expand hotkey markup if (fAllowHotkey && fMarkup) ExpandHotkeyMarkup(sText, cHotkey); } else { sText=""; cHotkey=0; } // update according to text only if autosize label (not wooden) if (!fAutosize) return; // get text extents pFont->GetTextExtent(sText.getData(), rcBounds.Wdt, rcBounds.Hgt, fMarkup); // update pos SetX0(x0); } void Label::UpdateOwnPos() { // update text drawing pos switch (iAlign) { case ALeft: x0 = rcBounds.x + GetLeftIndent(); break; case ACenter: x0 = rcBounds.x+rcBounds.Wdt/2; break; case ARight: x0 = rcBounds.x+rcBounds.Wdt; break; } } bool Label::OnHotkey(uint32_t cHotkey) { // if hotkey matches and focus control is assigned, set focus if (this->cHotkey == cHotkey && pClickFocusControl) { GetDlg()->SetFocus(pClickFocusControl, false); return true; } else return false; } void Label::SetX0(int32_t iToX0) { x0 = iToX0; // update x-startpos switch (iAlign) { case ALeft: rcBounds.x = x0; break; case ACenter: rcBounds.x = x0 - rcBounds.Wdt/2; break; case ARight: rcBounds.x = x0 - rcBounds.Wdt; break; } // size might have changed UpdateSize(); } // -------------------------------------------------- // WoodenLabel void WoodenLabel::DrawElement(C4TargetFacet &cgo) { // draw wood DrawBar(cgo, ::GraphicsResource.barCaption); // draw symbol if (fctIcon.Surface) { C4Facet cgoSymbol(cgo.Surface, cgo.TargetX + rcBounds.x + 1, cgo.TargetY + rcBounds.y + 1, rcBounds.Hgt-2, rcBounds.Hgt-2); fctIcon.Draw(cgoSymbol); } // calculations for automatic scrolling int32_t iXOff = 0; if (iAlign == ALeft) iXOff += 5; if (iAutoScrollDelay) { C4TimeMilliseconds tNow = C4TimeMilliseconds::Now(); if (tNow >= tLastChangeTime + iAutoScrollDelay) { if (!iScrollDir) iScrollDir=1; int32_t iMaxScroll = std::max(pFont->GetTextWidth(sText.getData(), true) + (x0 - rcBounds.x) + iXOff + GetRightIndent() - rcBounds.Wdt, 0); if (iMaxScroll) { iScrollPos += iScrollDir; if (iScrollPos >= iMaxScroll || iScrollPos < 0) { iScrollDir = -iScrollDir; iScrollPos += iScrollDir; tLastChangeTime = tNow; } } } iXOff -= iScrollPos; } // print out text; clipped pDraw->StorePrimaryClipper(); pDraw->SetPrimaryClipper(rcBounds.x + GetLeftIndent() + cgo.TargetX, rcBounds.y + cgo.TargetY, rcBounds.x+rcBounds.Wdt - GetRightIndent() + cgo.TargetX, rcBounds.y+rcBounds.Hgt + cgo.TargetY); pDraw->TextOut(sText.getData(), *pFont, 1.0f, cgo.Surface, x0 + cgo.TargetX + iXOff, rcBounds.y + cgo.TargetY + (rcBounds.Hgt-pFont->GetLineHeight())/2-1, dwFgClr, iAlign); pDraw->RestorePrimaryClipper(); } int32_t WoodenLabel::GetDefaultHeight(CStdFont *pUseFont) { if (!pUseFont) pUseFont = &(::GraphicsResource.TextFont); return std::max(pUseFont->GetLineHeight(), C4GUI_MinWoodBarHgt); } void WoodenLabel::SetIcon(const C4Facet &rfctIcon) { // set icon fctIcon = rfctIcon; // realign text to left for set icons if (fctIcon.Surface) iAlign = ALeft; else iAlign = ACenter; UpdateOwnPos(); } void WoodenLabel::ResetAutoScroll() { iScrollPos = iScrollDir = 0; } // -------------------------------------------------- // MultilineLabel MultilineLabel::MultilineLabel(const C4Rect &rcBounds, int32_t iMaxLines, int32_t iMaxBuf, const char *szIndentChars, bool fAutoGrow, bool fMarkup) // ctor : Element(), Lines(iMaxBuf, iMaxLines, rcBounds.Wdt, szIndentChars, fAutoGrow, fMarkup), fMarkup(fMarkup) { // set bounds this->rcBounds = rcBounds; // update height (min height) UpdateOwnPos(); } void MultilineLabel::DrawElement(C4TargetFacet &cgo) { // get clipping int iClipX, iClipY, iClipX2, iClipY2; pDraw->GetPrimaryClipper(iClipX, iClipY, iClipX2, iClipY2); // draw all lines int32_t iIndex = 0; const char *szLine; int32_t iY = rcBounds.y + cgo.TargetY; CStdFont *pLineFont; DWORD dwLineClr; bool fNewParagraph; while ((szLine = Lines.GetLine(iIndex, &pLineFont, &dwLineClr, &fNewParagraph))) { int32_t iFontLineHeight = pLineFont->GetLineHeight(); // indents between paragraphs if (fNewParagraph && iIndex) iY += iFontLineHeight/3; // clip if (iY > iClipY2) break; if (iY >= iClipY-iFontLineHeight) { // draw line pDraw->TextOut(szLine, *pLineFont, 1.0f, cgo.Surface, rcBounds.x + cgo.TargetX, iY, dwLineClr, ALeft, fMarkup); } // advance line iY += iFontLineHeight; ++iIndex; } } void MultilineLabel::UpdateSize() { // forward change to line buffer Lines.SetLBWidth(rcBounds.Wdt); UpdateHeight(); } void MultilineLabel::UpdateHeight() { // size by line count int32_t iIndex = 0; const char *szLine; int32_t iHgt = 0; CStdFont *pLineFont; bool fNewPar; while ((szLine = Lines.GetLine(iIndex, &pLineFont, nullptr, &fNewPar))) { int32_t iFontLineHeight = pLineFont->GetLineHeight(); // indents between separate messages if (fNewPar && iIndex) iHgt += iFontLineHeight/3; // text line height iHgt += iFontLineHeight; ++iIndex; } rcBounds.Hgt = std::max(iHgt, 5); // update parent container Element::UpdateSize(); } void MultilineLabel::AddLine(const char *szLine, CStdFont *pFont, DWORD dwClr, bool fDoUpdate, bool fMakeReadableOnBlack, CStdFont *pCaptionFont) { // make color readable if (fMakeReadableOnBlack) MakeColorReadableOnBlack(dwClr); // forward to line buffer if (szLine) Lines.AppendLines(szLine, pFont, dwClr, pCaptionFont); // adjust height if (fDoUpdate) UpdateSize(); } void MultilineLabel::Clear(bool fDoUpdate) { // forward to line buffer Lines.Clear(); // adjust height if (fDoUpdate) UpdateSize(); } // -------------------------------------------------- // HorizontalLine void HorizontalLine::DrawElement(C4TargetFacet &cgo) { // draw horizontal line int32_t iX1 = rcBounds.x + cgo.TargetX, iX2 = iX1 + rcBounds.Wdt, iY = rcBounds.y + cgo.TargetY; pDraw->DrawLineDw(cgo.Surface, (float)(iX1+1), (float)(iY+1), (float)(iX2-1), (float)(iY+1), dwShadowClr); pDraw->DrawLineDw(cgo.Surface, (float)iX1, (float)iY, (float)(iX2-2), (float)iY, dwClr); } // -------------------------------------------------- // ProgressBar void ProgressBar::DrawElement(C4TargetFacet &cgo) { // do not draw in negative progress if (iProgress<0) return; CStdFont &rFont = ::GraphicsResource.TextFont; // draw border Draw3DFrame(cgo); // calc progress width int32_t iProgressWdt = (rcBounds.Wdt-4) * iProgress / iMax; // draw progress ::GraphicsResource.fctProgressBar.DrawX(cgo.Surface, cgo.TargetX+rcBounds.x+2, cgo.TargetY+rcBounds.y+2, iProgressWdt, rcBounds.Hgt-2); // print out progress text char szPrg[32+1]; sprintf(szPrg, "%i%%", 100 * iProgress / iMax); pDraw->TextOut(szPrg, rFont, 1.0f, cgo.Surface, cgo.TargetX+rcBounds.GetMiddleX(), rcBounds.y + cgo.TargetY + (rcBounds.Hgt-rFont.GetLineHeight())/2-1, C4GUI_ProgressBarFontClr, ACenter); } // -------------------------------------------------- // Picture Picture::Picture(const C4Rect &rcBounds, bool fAspect) : fCustomDrawClr(false), fAnimate(false) { // set values this->fAspect = fAspect; this->rcBounds = rcBounds; // no facet yet } void Picture::DrawElement(C4TargetFacet &cgo) { // animation? C4Facet *pDrawFacet, DrawFacet; if (fAnimate) { if (++iPhaseTime > iDelay) { int32_t iPhasesX=1, iPhasesY=1; Facet.GetPhaseNum(iPhasesX, iPhasesY); if (++iAnimationPhase >= iPhasesX) iAnimationPhase = 0; iPhaseTime = 0; } DrawFacet = Facet.GetPhase(iAnimationPhase); pDrawFacet = &DrawFacet; } else pDrawFacet = &Facet; // draw the facet C4Facet cgo2 = cgo; cgo2.X = rcBounds.x + cgo.TargetX; cgo2.Y = rcBounds.y + cgo.TargetY; cgo2.Wdt = rcBounds.Wdt; cgo2.Hgt = rcBounds.Hgt; if (fCustomDrawClr) { pDrawFacet->DrawClr(cgo2, fAspect, dwDrawClr); } else pDrawFacet->Draw(cgo2, fAspect); } bool Picture::EnsureOwnSurface() { // no surface? if (!Facet.Surface) return false; // equals face already? if (Facet.Surface == &Facet.GetFace()) return true; // then create as a copy C4Facet cgo = Facet; if (!Facet.Create(cgo.Wdt, cgo.Hgt)) return false; cgo.Draw(Facet); return true; } void Picture::SetAnimated(bool fEnabled, int iDelay) { if ((fAnimate = fEnabled)) { // starts cycling through all phases of the specified facet iAnimationPhase=iPhaseTime=0; this->iDelay = iDelay; } } // -------------------------------------------------- // OverlayPicture OverlayPicture::OverlayPicture(const C4Rect &rcBounds, bool fAspect, const C4Facet &rOverlayImage, int iBorderSize) : Picture(rcBounds, fAspect), iBorderSize(iBorderSize), OverlayImage(rOverlayImage) { } void OverlayPicture::DrawElement(C4TargetFacet &cgo) { // draw inner image C4Facet cgo2 = cgo; cgo2.X = rcBounds.x + cgo.TargetX + iBorderSize * rcBounds.Wdt / std::max(OverlayImage.Wdt, 1); cgo2.Y = rcBounds.y + cgo.TargetY + iBorderSize * rcBounds.Hgt / std::max(OverlayImage.Hgt, 1); cgo2.Wdt = rcBounds.Wdt - 2 * iBorderSize * rcBounds.Wdt / std::max(OverlayImage.Wdt, 1); cgo2.Hgt = rcBounds.Hgt - 2 * iBorderSize * rcBounds.Hgt / std::max(OverlayImage.Hgt, 1); Facet.Draw(cgo2, fAspect); // draw outer image cgo2.X = rcBounds.x + cgo.TargetX; cgo2.Y = rcBounds.y + cgo.TargetY; cgo2.Wdt = rcBounds.Wdt; cgo2.Hgt = rcBounds.Hgt; OverlayImage.Draw(cgo2, fAspect); } // -------------------------------------------------- // Icon Icon::Icon(const C4Rect &rcBounds, Icons icoIconIndex) : Picture(rcBounds, true) { // set icon facet SetIcon(icoIconIndex); } void Icon::SetIcon(Icons icoNewIconIndex) { // load icon SetFacet(GetIconFacet(icoNewIconIndex)); } C4Facet Icon::GetIconFacet(Icons icoIconIndex) { if (icoIconIndex == Ico_None) return C4Facet(); C4Facet *rFacet; switch (icoIconIndex & ~0xff) { case Ico_Extended: rFacet = &::GraphicsResource.fctIconsEx; break; case Ico_Controller: rFacet = &::GraphicsResource.fctControllerIcons; break; default: rFacet = &::GraphicsResource.fctIcons; } icoIconIndex = Icons(icoIconIndex & 0xff); int32_t iXMax, iYMax; rFacet->GetPhaseNum(iXMax, iYMax); if (!iXMax) iXMax = 6; return rFacet->GetPhase(icoIconIndex % iXMax, icoIconIndex / iXMax); } // -------------------------------------------------- // PaintBox void PaintBox::MouseInput(CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam) { } void PaintBox::DrawElement(C4TargetFacet &cgo) { // draw it fctPaint.Draw(cgo.Surface, rcBounds.x+cgo.TargetX, rcBounds.y+cgo.TargetY); } PaintBox::PaintBox(C4Rect &rtBounds, int32_t iSfcWdt, int32_t iSfcHgt) { // default surface bounds if (iSfcWdt<0) iSfcWdt = rtBounds.Wdt; if (iSfcHgt<0) iSfcHgt = rtBounds.Hgt; // create surface if (iSfcWdt<=0 || iSfcHgt<=0) return; fctPaint.Create(iSfcWdt, iSfcHgt); } PaintBox::~PaintBox() = default; // -------------------------------------------------- // TextWindow TextWindow::TextWindow(C4Rect &rtBounds, size_t iPicWdt, size_t iPicHgt, size_t iPicPadding, size_t iMaxLines, size_t iMaxTextLen, const char *szIndentChars, bool fAutoGrow, const C4Facet *pOverlayPic, int iOverlayBorder, bool fMarkup) : Control(rtBounds), pLogBuffer(nullptr), fDrawBackground(true), fDrawFrame(true), iPicPadding(iPicPadding) { // calc client rect UpdateOwnPos(); // create content scroll window pClientWindow = new ScrollWindow(this); pClientWindow->SetBounds(GetContainedClientRect()); // create content multiline label pLogBuffer = new MultilineLabel(pClientWindow->GetContainedClientRect(), iMaxLines, iMaxTextLen, szIndentChars, fAutoGrow, fMarkup); // add to scroll window pClientWindow->AddElement(pLogBuffer); // update scrolling (for empty buffer) pClientWindow->SetClientHeight(1); // create content picture, if desired C4Rect rcContentSize = pClientWindow->GetClientRect(); if (iPicWdt && iPicHgt) { C4Rect rcImage; rcImage.x = std::max(rcContentSize.GetMiddleX() - iPicWdt/2, 0); rcImage.y = 0; rcImage.Wdt = std::min(iPicWdt, rcContentSize.Wdt); rcImage.Hgt = iPicHgt * rcImage.Wdt / iPicWdt; rcContentSize.y += rcImage.Hgt + iPicPadding; if (pOverlayPic) pTitlePicture = new OverlayPicture(rcImage, false, *pOverlayPic, iOverlayBorder); else pTitlePicture = new Picture(rcImage, false); pClientWindow->AddElement(pTitlePicture); } else pTitlePicture = nullptr; // update size UpdateSize(); } void TextWindow::UpdateSize() { Control::UpdateSize(); pClientWindow->SetBounds(GetContainedClientRect()); // resize log buffer pos to horizontal extents C4Rect rcChildBounds = pLogBuffer->GetBounds(); rcChildBounds.x = 0; rcChildBounds.y = (pTitlePicture && pTitlePicture->IsVisible()) ? pTitlePicture->GetBounds().Hgt + iPicPadding : 0; rcChildBounds.Wdt = pClientWindow->GetClientRect().Wdt; pLogBuffer->SetBounds(rcChildBounds); } void TextWindow::DrawElement(C4TargetFacet &cgo) { // draw background if (fDrawBackground) pDraw->DrawBoxDw(cgo.Surface, cgo.TargetX+rcBounds.x,cgo.TargetY+rcBounds.y,rcBounds.x+rcBounds.Wdt-1+cgo.TargetX,rcBounds.y+rcBounds.Hgt-1+cgo.TargetY,0x7f000000); // draw frame if (fDrawFrame) Draw3DFrame(cgo); } void TextWindow::ElementSizeChanged(Element *pOfElement) { // inherited if (pOfElement->GetParent() == this) Control::ElementSizeChanged(pOfElement); // update size of scroll control if (pClientWindow && pLogBuffer) pClientWindow->SetClientHeight(pLogBuffer->GetBounds().y + pLogBuffer->GetBounds().Hgt); } void TextWindow::ElementPosChanged(Element *pOfElement) { // inherited if (pOfElement->GetParent() == this) Control::ElementSizeChanged(pOfElement); // update size of scroll control if (pClientWindow && pLogBuffer) pClientWindow->SetClientHeight(pLogBuffer->GetBounds().y + pLogBuffer->GetBounds().Hgt); } void TextWindow::SetPicture(const C4Facet &rNewPic) { // update picture if (!pTitlePicture) return; pTitlePicture->SetFacet(rNewPic); // reposition multiline label below picture if any is assigned pLogBuffer->GetBounds().y = rNewPic.Surface ? pTitlePicture->GetBounds().Hgt + iPicPadding : 0; pLogBuffer->UpdateOwnPos(); pTitlePicture->SetVisibility(!!rNewPic.Surface); } } // end of namespace