colobot/src/ui/controls/list.cpp

870 lines
21 KiB
C++

/*
* This file is part of the Colobot: Gold Edition source code
* Copyright (C) 2001-2021, Daniel Roux, EPSITEC SA & TerranovaTeam
* http://epsitec.ch; http://colobot.info; http://github.com/colobot
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://gnu.org/licenses
*/
#include "ui/controls/list.h"
#include "common/make_unique.h"
#include "graphics/core/device.h"
#include "graphics/core/renderers.h"
#include "graphics/engine/engine.h"
#include "math/func.h"
#include <cassert>
#include <cstring>
namespace Ui
{
const float MARGING = 4.0f;
// Object's constructor.
CList::CList()
: CControl(),
m_tabs(),
m_justifs()
{
for (int i = 0; i < 10; i++)
{
m_tabs[i] = 0.0f;
m_justifs[i] = Gfx::TEXT_ALIGN_LEFT;
}
m_expand = 0.0f;
m_totalLine = 0;
m_displayLine = 0;
m_selectLine = -1;
m_firstLine = 0;
m_bBlink = false;
m_bSelectCap = true;
m_blinkTime = 0.0f;
}
// Object's destructor.
CList::~CList()
{
}
// Creates a new list.
// if expand is less then zero, then the list would try to use expand's absolute value,
// and try to scale items to some size, so that dim of the list would not change after
// adjusting
bool CList::Create(const glm::vec2& pos, const glm::vec2& dim, int icon, EventType eventMsg, float expand)
{
m_expand = expand;
if (eventMsg == EVENT_NULL)
eventMsg = GetUniqueEventType();
CControl::Create(pos, dim, icon, eventMsg);
m_scroll = MakeUnique<CScroll>();
m_scroll->Create(pos, dim, 0, EVENT_NULL);
return MoveAdjust();
}
// Should never be called
bool CList::Create(const glm::vec2& pos, const glm::vec2& dim, int icon, EventType eventType)
{
assert(false);
return false;
}
// Adjusted after a change of dimensions.
bool CList::MoveAdjust()
{
glm::vec2 ipos, idim, ppos, ddim;
float marging, h;
for (auto& button : m_buttons)
button.reset();
if (m_icon == 0)
marging = MARGING;
else
marging = 0.0f;
ipos.x = m_pos.x + marging / 640.f;
ipos.y = m_pos.y + marging / 480.f;
idim.x = m_dim.x - marging * 2.0f / 640.f;
idim.y = m_dim.y - marging * 2.0f / 480.f;
//If m_expand is less then zero, then try to apply it's absolute value
h = m_engine->GetText()->GetHeight(m_fontType, m_fontSize) * ((m_expand < 0) ? -m_expand : m_expand);
m_displayLine = static_cast<int>(idim.y / h);
if (m_displayLine == 0)
return false;
if (m_displayLine > LISTMAXDISPLAY)
m_displayLine = LISTMAXDISPLAY;
// Stretch lines to fill whole area of a list, if needed
if (m_expand < 0 && (idim.y - (h * m_displayLine) < h))
{
h = idim.y / m_displayLine;
}
idim.y = h * m_displayLine; //Here cuts list size if height of shown elements is less then designed height
m_dim.y = idim.y + marging * 2.0f / 480.f;
ppos.x = ipos.x;
ppos.y = ipos.y + idim.y - h;
ddim.x = idim.x - SCROLL_WIDTH;
ddim.y = h;
for (int i = 0; i < m_displayLine; i++)
{
auto button = MakeUnique<CButton>();
button->Create(ppos, ddim, -1, EVENT_NULL);
button->SetTextAlign(Gfx::TEXT_ALIGN_LEFT);
button->SetState(STATE_SIMPLY);
button->SetFontType(m_fontType);
button->SetFontSize(m_fontSize);
ppos.y -= h;
m_buttons[i] = std::move(button);
}
if (m_scroll != nullptr)
{
ppos.x = ipos.x + idim.x - SCROLL_WIDTH;
ppos.y = ipos.y;
ddim.x = SCROLL_WIDTH;
ddim.y = idim.y;
m_scroll->SetPos(ppos);
m_scroll->SetDim(ddim);
}
UpdateScroll();
UpdateButton();
return true;
}
// Returns the message of a button.
EventType CList::GetEventMsgButton(int i)
{
if (i < 0 || i >= m_displayLine)
return EVENT_NULL;
if (m_buttons[i] == nullptr)
return EVENT_NULL;
return m_buttons[i]->GetEventType();
}
// Returns the message from the elevator.
EventType CList::GetEventMsgScroll()
{
if (m_scroll == nullptr)
return EVENT_NULL;
return m_scroll->GetEventType();
}
void CList::SetPos(const glm::vec2& pos)
{
CControl::SetPos(pos);
}
void CList::SetDim(const glm::vec2& dim)
{
m_dim = dim;
MoveAdjust();
CControl::SetDim(dim);
}
bool CList::SetState(int state, bool bState)
{
if (state & STATE_ENABLE)
{
for (int i = 0; i < m_displayLine; i++)
{
if (m_buttons[i] != nullptr)
m_buttons[i]->SetState(state, bState);
}
if (m_scroll != nullptr)
m_scroll->SetState(state, bState);
}
return CControl::SetState(state, bState);
}
bool CList::SetState(int state)
{
if (state & STATE_ENABLE)
{
for (int i = 0; i < m_displayLine; i++)
{
if (m_buttons[i] != nullptr)
m_buttons[i]->SetState(state);
}
if (m_scroll != nullptr)
m_scroll->SetState(state);
}
return CControl::SetState(state);
}
bool CList::ClearState(int state)
{
if (state & STATE_ENABLE)
{
for (int i = 0; i < m_displayLine; i++)
{
if (m_buttons[i] != nullptr)
m_buttons[i]->ClearState(state);
}
if (m_scroll != nullptr)
m_scroll->ClearState(state);
}
return CControl::ClearState(state);
}
// Management of an event.
bool CList::EventProcess(const Event &event)
{
if (m_bBlink && event.type == EVENT_FRAME)
{
int i = m_selectLine-m_firstLine;
if (i >= 0 && i < 4 && m_buttons[i] != nullptr)
{
m_blinkTime += event.rTime;
if (Math::Mod(m_blinkTime, 0.7f) < 0.3f)
{
m_buttons[i]->ClearState(STATE_ENABLE);
m_buttons[i]->ClearState(STATE_CHECK);
}
else
{
m_buttons[i]->SetState(STATE_ENABLE);
m_buttons[i]->SetState(STATE_CHECK);
}
}
}
if ((m_state & STATE_VISIBLE) == 0)
return true;
if ((m_state & STATE_ENABLE) == 0)
return true;
if (event.type == EVENT_MOUSE_WHEEL && Detect(event.mousePos))
{
auto data = event.GetData<MouseWheelEventData>();
m_firstLine -= data->y;
if (m_firstLine > m_totalLine - m_displayLine)
m_firstLine = m_totalLine - m_displayLine;
if (m_firstLine < 0)
m_firstLine = 0;
UpdateScroll();
UpdateButton();
return false;
}
CControl::EventProcess(event);
if (event.type == EVENT_MOUSE_MOVE || event.type == EVENT_MOUSE_BUTTON_DOWN || event.type == EVENT_MOUSE_BUTTON_UP)
{
if (Detect(event.mousePos))
{
m_engine->SetMouseType(Gfx::ENG_MOUSE_NORM);
for (int i = 0; i < m_displayLine; i++)
{
if (i + m_firstLine >= m_totalLine)
break;
if (m_buttons[i] != nullptr)
m_buttons[i]->EventProcess(event);
}
}
}
if (m_bSelectCap)
{
for (int i = 0; i < m_displayLine; i++)
{
if (i + m_firstLine >= m_totalLine)
break;
if (m_buttons[i] != nullptr)
{
if (!m_buttons[i]->EventProcess(event))
return false;
if (event.type == m_buttons[i]->GetEventType())
{
SetSelect(m_firstLine + i);
m_event->AddEvent(Event(m_eventType)); // selected line changes
}
}
}
}
if (m_scroll != nullptr)
{
if (!m_scroll->EventProcess(event))
return false;
if (event.type == m_scroll->GetEventType())
{
MoveScroll();
UpdateButton();
}
}
return true;
}
// Draws the list.
void CList::Draw()
{
glm::vec2 uv1, uv2, corner, pos, dim, ppos, ddim;
float dp;
int i;
char text[100];
const char *pb, *pe;
if ((m_state & STATE_VISIBLE) == 0)
return;
if (m_state & STATE_SHADOW)
DrawShadow(m_pos, m_dim);
dp = 0.5f / 256.0f;
auto renderer = m_engine->GetDevice()->GetUIRenderer();
if (m_icon != -1)
{
dim = m_dim;
if (m_icon == 0)
{
auto texture = m_engine->LoadTexture("textures/interface/button2.png");
renderer->SetTexture(texture);
renderer->SetTransparency(Gfx::TransparencyMode::NONE);
uv1.x = 128.0f / 256.0f;
uv1.y = 64.0f / 256.0f; // u-v texture
uv2.x = 160.0f / 256.0f;
uv2.y = 96.0f / 256.0f;
}
else
{
auto texture = m_engine->LoadTexture("textures/interface/button2.png");
renderer->SetTexture(texture);
renderer->SetTransparency(Gfx::TransparencyMode::NONE);
uv1.x = 132.0f / 256.0f;
uv1.y = 68.0f / 256.0f; // u-v texture
uv2.x = 156.0f / 256.0f;
uv2.y = 92.0f / 256.0f;
if (m_buttons[0] != nullptr)
{
dim = m_buttons[0]->GetDim();
dim.y *= m_displayLine; // background sounds spot behind
}
}
uv1.x += dp;
uv1.y += dp;
uv2.x -= dp;
uv2.y -= dp;
corner.x = 10.0f / 640.0f;
corner.y = 10.0f / 480.0f;
DrawIcon(m_pos, dim, uv1, uv2, corner, 8.0f / 256.0f);
}
if ( m_totalLine < m_displayLine ) // no buttons to the bottom?
{
i = m_totalLine;
if (m_buttons[i] != nullptr)
{
pos = m_buttons[i]->GetPos();
dim = m_buttons[i]->GetDim();
pos.y += dim.y * 1.1f;
dim.y *= 0.4f;
pos.y -= dim.y;
auto texture = m_engine->LoadTexture("textures/interface/button2.png");
renderer->SetTexture(texture);
renderer->SetTransparency(Gfx::TransparencyMode::WHITE);
uv1.x = 120.0f / 256.0f;
uv1.y = 64.0f / 256.0f;
uv2.x = 128.0f / 256.0f;
uv2.y = 48.0f / 256.0f;
uv1.x += dp;
uv1.y -= dp;
uv2.x -= dp;
uv2.y += dp;
DrawIcon(pos, dim, uv1, uv2); // ch'tite shadow cute (?)
}
}
for (i = 0; i < m_displayLine; i++)
{
if ( i + m_firstLine >= m_totalLine )
break;
if ( m_buttons[i] != nullptr )
{
if ( !m_bBlink && i + m_firstLine < m_totalLine )
m_buttons[i]->SetState(STATE_ENABLE, m_items[i+m_firstLine].enable && (m_state & STATE_ENABLE) );
m_buttons[i]->Draw(); // draws a box without text
// draws text in the box
pos = m_buttons[i]->GetPos();
dim = m_buttons[i]->GetDim();
if ( m_tabs[0] == 0.0f )
{
ppos.x = pos.x + dim.y * 0.5f;
ppos.y = pos.y + dim.y * 0.5f;
ppos.y -= m_engine->GetText()->GetHeight(m_fontType, m_fontSize) / 2.0f;
ddim.x = dim.x-dim.y;
DrawCase(m_items[i + m_firstLine].text.c_str(), ppos, ddim.x, Gfx::TEXT_ALIGN_LEFT);
}
else
{
ppos.x = pos.x + dim.y * 0.5f;
ppos.y = pos.y + dim.y * 0.5f;
ppos.y -= m_engine->GetText()->GetHeight(m_fontType, m_fontSize) / 2.0f;
pb = m_items[i + m_firstLine].text.c_str();
for (int j = 0; j < 10; j++)
{
pe = strchr(pb, '\t');
if ( pe == nullptr )
strcpy(text, pb);
else
{
strncpy(text, pb, pe - pb);
text[pe - pb] = 0;
}
DrawCase(text, ppos, m_tabs[j], m_justifs[j]);
if ( pe == nullptr )
break;
ppos.x += m_tabs[j];
pb = pe + 1;
}
}
if ( (m_state & STATE_EXTEND) && i < m_totalLine)
{
pos = m_buttons[i]->GetPos();
dim = m_buttons[i]->GetDim();
pos.x += dim.x - dim.y * 0.75f;
dim.x = dim.y * 0.75f;
pos.x += 2.0f / 640.0f;
pos.y += 2.0f / 480.0f;
dim.x -= 4.0f / 640.0f;
dim.y -= 4.0f / 480.0f;
if (m_items[i + m_firstLine].check)
{
auto texture = m_engine->LoadTexture("textures/interface/button1.png");
renderer->SetTexture(texture);
renderer->SetTransparency(Gfx::TransparencyMode::NONE);
uv1.x = 64.0f / 256.0f;
uv1.y = 0.0f / 256.0f;
uv2.x = 96.0f / 256.0f;
uv2.y = 32.0f / 256.0f;
uv1.x += dp;
uv1.y += dp;
uv2.x -= dp;
uv2.y -= dp;
DrawIcon(pos, dim, uv1, uv2); // square shape
renderer->SetTransparency(Gfx::TransparencyMode::WHITE);
uv1.x = 0.0f / 256.0f; // v
uv1.y = 64.0f / 256.0f;
uv2.x = 32.0f / 256.0f;
uv2.y = 96.0f / 256.0f;
uv1.x += dp;
uv1.y += dp;
uv2.x -= dp;
uv2.y -= dp;
DrawIcon(pos, dim, uv1, uv2); // draws v
}
else
{
auto texture = m_engine->LoadTexture("textures/interface/button1.png");
renderer->SetTexture(texture);
renderer->SetTransparency(Gfx::TransparencyMode::WHITE);
if ( i + m_firstLine == m_selectLine )
{
uv1.x =224.0f / 256.0f; // <
uv1.y =192.0f / 256.0f;
uv2.x =256.0f / 256.0f;
uv2.y =224.0f / 256.0f;
}
else
{
uv1.x = 96.0f / 256.0f; // x
uv1.y = 32.0f / 256.0f;
uv2.x =128.0f / 256.0f;
uv2.y = 64.0f / 256.0f;
}
uv1.x += dp;
uv1.y += dp;
uv2.x -= dp;
uv2.y -= dp;
DrawIcon(pos, dim, uv1, uv2); // draws x
}
}
}
}
if (m_scroll != nullptr)
m_scroll->Draw(); // draws the lift
}
// Displays text in a box.
void CList::DrawCase(const char* text, const glm::vec2& position, float width, Gfx::TextAlign justif)
{
glm::vec2 pos = position;
if (justif == Gfx::TEXT_ALIGN_CENTER)
pos.x += width / 2.0f;
else if (justif == Gfx::TEXT_ALIGN_RIGHT)
pos.x += width;
m_engine->GetText()->DrawText(std::string(text), m_fontType, m_fontSize, pos, width, justif, 0);
}
// Empty the list completely.
void CList::Flush()
{
m_totalLine = 0;
m_selectLine = -1;
m_firstLine = 0;
UpdateButton();
UpdateScroll();
}
// Specifies the total number of lines.
void CList::SetTotal(int i)
{
m_totalLine = i;
}
// Returns the total number of lines.
int CList::GetTotal()
{
return m_totalLine;
}
// Selects a line.
void CList::SetSelect(int i)
{
if ( m_bSelectCap )
m_selectLine = i;
else
{
m_firstLine = i;
UpdateScroll();
}
UpdateButton();
}
// Returns the selected line.
int CList::GetSelect()
{
if ( m_bSelectCap )
return m_selectLine;
else
return m_firstLine;
}
// Management of capability has a select box.
void CList::SetSelectCap(bool bEnable)
{
m_bSelectCap = bEnable;
}
bool CList::GetSelectCap()
{
return m_bSelectCap;
}
// Blink a line.
void CList::SetBlink(bool bEnable)
{
m_bBlink = bEnable;
m_blinkTime = 0.0f;
int i = m_selectLine - m_firstLine;
if (i >= 0 && i < 4 && m_buttons[i] != nullptr)
{
if ( !bEnable )
{
m_buttons[i]->SetState(STATE_CHECK);
m_buttons[i]->ClearState(STATE_ENABLE);
}
}
}
bool CList::GetBlink()
{
return m_bBlink;
}
// Specifies the text of a line.
void CList::SetItemName(int i, const std::string& name)
{
if ( i < 0 )
return;
if ( i >= m_totalLine )
m_totalLine = i+1; // expands the list
m_items.resize(m_totalLine);
if ( name.empty() )
m_items[i].text = " ";
else
m_items[i].text = name;
UpdateButton();
UpdateScroll();
}
// Returns the text of a line.
const std::string& CList::GetItemName(int i)
{
if ( i < 0 || i >= m_totalLine )
assert(false);
return m_items[i].text;
}
// Specifies the bit "check" for a box.
void CList::SetCheck(int i, bool bMode)
{
if ( i < 0 || i >= m_totalLine )
return;
m_items[i].check = bMode;
}
// Returns the bit "check" for a box.
bool CList::GetCheck(int i)
{
if ( i < 0 || i >= m_totalLine )
return false;
return m_items[i].check;
}
// Specifies the bit "enable" for a box.
void CList::SetEnable(int i, bool enable)
{
if ( i < 0 || i >= m_totalLine )
return;
m_items[i].enable = enable;
}
// Returns the bit "enable" for a box.
bool CList::GetEnable(int i)
{
if ( i < 0 || i >= m_totalLine )
return false;
return m_items[i].enable;
}
// Management of the position of the tabs.
void CList::SetTabs(int i, float pos, Gfx::TextAlign justif)
{
if ( i < 0 || i >= 10 )
return;
m_tabs[i] = pos;
m_justifs[i] = justif;
}
float CList::GetTabs(int i)
{
if ( i < 0 || i >= 10 )
return 0.0f;
return m_tabs[i];
}
// Moves the lift to see the list of the selected line.
void CList::ShowSelect(bool bFixed)
{
int sel;
if ( bFixed && m_selectLine >= m_firstLine && m_selectLine < m_firstLine+m_displayLine )
return; // all good
sel = m_selectLine;
// Down from 1/2 * h.
sel += m_displayLine / 2;
if ( sel > m_totalLine - 1 )
sel = m_totalLine - 1;
// Back to h-1.
sel -= m_displayLine - 1;
if ( sel < 0 )
sel = 0;
m_firstLine = sel;
UpdateButton();
UpdateScroll();
}
// Updates all button names.
void CList::UpdateButton()
{
int state, i, j;
state = CControl::GetState();
j = m_firstLine;
for (i = 0; i < m_displayLine; i++)
{
if (m_buttons[i] == nullptr)
continue;
m_buttons[i]->SetState(STATE_CHECK, (j == m_selectLine));
if ( j < m_totalLine )
{
//? m_buttons[i]->SetName(m_text[j]);
m_buttons[i]->SetName(" "); // blank button
m_buttons[i]->SetState(STATE_ENABLE, (state & STATE_ENABLE));
}
else
{
m_buttons[i]->SetName(" "); // blank button
m_buttons[i]->ClearState(STATE_ENABLE);
}
j ++;
}
}
// Updates the lift.
void CList::UpdateScroll()
{
float ratio, value, step;
if (m_scroll == nullptr)
return;
if (m_totalLine <= m_displayLine)
{
ratio = 1.0f;
value = 0.0f;
step = 0.0f;
}
else
{
ratio = static_cast<float>(m_displayLine) / m_totalLine;
if ( ratio > 1.0f ) ratio = 1.0f;
value = static_cast<float>(m_firstLine) / (m_totalLine - m_displayLine);
if ( value < 0.0f )
value = 0.0f;
if ( value > 1.0f )
value = 1.0f;
step = static_cast<float>(1.0f)/ (m_totalLine - m_displayLine);
if ( step < 0.0f )
step = 0.0f;
}
m_scroll->SetVisibleRatio(ratio);
m_scroll->SetVisibleValue(value);
m_scroll->SetArrowStep(step);
}
// Update when the lift is moving.
void CList::MoveScroll()
{
if (m_scroll == nullptr)
return;
int n = m_totalLine - m_displayLine;
float pos = m_scroll->GetVisibleValue();
pos += m_scroll->GetArrowStep() / 2.0f; // it's magic!
m_firstLine = static_cast<int>(pos * n);
if ( m_firstLine < 0 )
m_firstLine = 0;
if ( m_firstLine > n )
m_firstLine = n;
}
} // namespace Ui