mirror of
https://github.com/minetest/minetest.git
synced 2024-08-18 20:44:16 +02:00
Chat console, including a number of rebases and modifications.
Defaults modified from original: alpha=200, key=F10
This commit is contained in:
parent
0053651814
commit
967f25461b
18
README.txt
18
README.txt
@ -277,3 +277,21 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN THE SOFTWARE.
|
||||
|
||||
|
||||
Fonts
|
||||
---------------
|
||||
|
||||
DejaVu Sans Mono:
|
||||
|
||||
Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
|
||||
Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below)
|
||||
|
||||
Bitstream Vera Fonts Copyright:
|
||||
|
||||
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
|
||||
a trademark of Bitstream, Inc.
|
||||
|
||||
Arev Fonts Copyright:
|
||||
|
||||
Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
|
||||
|
||||
|
||||
|
@ -42,6 +42,8 @@
|
||||
# Go down ladder / go down in fly mode / go fast in fast mode
|
||||
#keymap_special1 = KEY_KEY_E
|
||||
#keymap_chat = KEY_KEY_T
|
||||
#keymap_cmd = /
|
||||
#keyman_console = KEY_F10
|
||||
#keymap_rangeselect = KEY_KEY_R
|
||||
#keymap_freemove = KEY_KEY_K
|
||||
#keymap_fastmove = KEY_KEY_J
|
||||
@ -104,6 +106,10 @@
|
||||
#screenshot_path = .
|
||||
# Amount of view bobbing (0 = no view bobbing, 1.0 = normal, 2.0 = double)
|
||||
#view_bobbing_amount = 1.0
|
||||
# In-game chat console background color (R,G,B)
|
||||
#console_color = (0,0,0)
|
||||
# In-game chat console background alpha (opaqueness, between 0 and 255)
|
||||
#console_alpha = 200
|
||||
|
||||
#
|
||||
# Server stuff
|
||||
|
BIN
share/client/textures/fontdejavusansmono.png
Normal file
BIN
share/client/textures/fontdejavusansmono.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.2 KiB |
@ -167,6 +167,7 @@ set(minetest_SRCS
|
||||
camera.cpp
|
||||
clouds.cpp
|
||||
clientobject.cpp
|
||||
chat.cpp
|
||||
guiMainMenu.cpp
|
||||
guiKeyChangeMenu.cpp
|
||||
guiMessageMenu.cpp
|
||||
@ -175,6 +176,7 @@ set(minetest_SRCS
|
||||
guiPauseMenu.cpp
|
||||
guiPasswordChange.cpp
|
||||
guiDeathScreen.cpp
|
||||
guiChatConsole.cpp
|
||||
client.cpp
|
||||
tile.cpp
|
||||
game.cpp
|
||||
|
768
src/chat.cpp
Normal file
768
src/chat.cpp
Normal file
@ -0,0 +1,768 @@
|
||||
/*
|
||||
Minetest-c55
|
||||
Copyright (C) 2011 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||
|
||||
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 2 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, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "chat.h"
|
||||
#include "debug.h"
|
||||
#include "utility.h"
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
#include <sstream>
|
||||
|
||||
ChatBuffer::ChatBuffer(u32 scrollback):
|
||||
m_scrollback(scrollback),
|
||||
m_unformatted(),
|
||||
m_cols(0),
|
||||
m_rows(0),
|
||||
m_scroll(0),
|
||||
m_formatted(),
|
||||
m_empty_formatted_line()
|
||||
{
|
||||
if (m_scrollback == 0)
|
||||
m_scrollback = 1;
|
||||
m_empty_formatted_line.first = true;
|
||||
}
|
||||
|
||||
ChatBuffer::~ChatBuffer()
|
||||
{
|
||||
}
|
||||
|
||||
void ChatBuffer::addLine(std::wstring name, std::wstring text)
|
||||
{
|
||||
ChatLine line(name, text);
|
||||
m_unformatted.push_back(line);
|
||||
|
||||
if (m_rows > 0)
|
||||
{
|
||||
// m_formatted is valid and must be kept valid
|
||||
bool scrolled_at_bottom = (m_scroll == getBottomScrollPos());
|
||||
u32 num_added = formatChatLine(line, m_cols, m_formatted);
|
||||
if (scrolled_at_bottom)
|
||||
m_scroll += num_added;
|
||||
}
|
||||
|
||||
// Limit number of lines by m_scrollback
|
||||
if (m_unformatted.size() > m_scrollback)
|
||||
{
|
||||
deleteOldest(m_unformatted.size() - m_scrollback);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatBuffer::clear()
|
||||
{
|
||||
m_unformatted.clear();
|
||||
m_formatted.clear();
|
||||
m_scroll = 0;
|
||||
}
|
||||
|
||||
u32 ChatBuffer::getLineCount() const
|
||||
{
|
||||
return m_unformatted.size();
|
||||
}
|
||||
|
||||
u32 ChatBuffer::getScrollback() const
|
||||
{
|
||||
return m_scrollback;
|
||||
}
|
||||
|
||||
const ChatLine& ChatBuffer::getLine(u32 index) const
|
||||
{
|
||||
assert(index < getLineCount());
|
||||
return m_unformatted[index];
|
||||
}
|
||||
|
||||
void ChatBuffer::step(f32 dtime)
|
||||
{
|
||||
for (u32 i = 0; i < m_unformatted.size(); ++i)
|
||||
{
|
||||
m_unformatted[i].age += dtime;
|
||||
}
|
||||
}
|
||||
|
||||
void ChatBuffer::deleteOldest(u32 count)
|
||||
{
|
||||
u32 del_unformatted = 0;
|
||||
u32 del_formatted = 0;
|
||||
|
||||
while (count > 0 && del_unformatted < m_unformatted.size())
|
||||
{
|
||||
++del_unformatted;
|
||||
|
||||
// keep m_formatted in sync
|
||||
if (del_formatted < m_formatted.size())
|
||||
{
|
||||
assert(m_formatted[del_formatted].first);
|
||||
++del_formatted;
|
||||
while (del_formatted < m_formatted.size() &&
|
||||
!m_formatted[del_formatted].first)
|
||||
++del_formatted;
|
||||
}
|
||||
|
||||
--count;
|
||||
}
|
||||
|
||||
m_unformatted.erase(0, del_unformatted);
|
||||
m_formatted.erase(0, del_formatted);
|
||||
}
|
||||
|
||||
void ChatBuffer::deleteByAge(f32 maxAge)
|
||||
{
|
||||
u32 count = 0;
|
||||
while (count < m_unformatted.size() && m_unformatted[count].age > maxAge)
|
||||
++count;
|
||||
deleteOldest(count);
|
||||
}
|
||||
|
||||
u32 ChatBuffer::getColumns() const
|
||||
{
|
||||
return m_cols;
|
||||
}
|
||||
|
||||
u32 ChatBuffer::getRows() const
|
||||
{
|
||||
return m_rows;
|
||||
}
|
||||
|
||||
void ChatBuffer::reformat(u32 cols, u32 rows)
|
||||
{
|
||||
if (cols == 0 || rows == 0)
|
||||
{
|
||||
// Clear formatted buffer
|
||||
m_cols = 0;
|
||||
m_rows = 0;
|
||||
m_scroll = 0;
|
||||
m_formatted.clear();
|
||||
}
|
||||
else if (cols != m_cols || rows != m_rows)
|
||||
{
|
||||
// TODO: Avoid reformatting ALL lines (even inivisble ones)
|
||||
// each time the console size changes.
|
||||
|
||||
// Find out the scroll position in *unformatted* lines
|
||||
u32 restore_scroll_unformatted = 0;
|
||||
u32 restore_scroll_formatted = 0;
|
||||
bool at_bottom = (m_scroll == getBottomScrollPos());
|
||||
if (!at_bottom)
|
||||
{
|
||||
for (s32 i = 0; i < m_scroll; ++i)
|
||||
{
|
||||
if (m_formatted[i].first)
|
||||
++restore_scroll_unformatted;
|
||||
}
|
||||
}
|
||||
|
||||
// If number of columns change, reformat everything
|
||||
if (cols != m_cols)
|
||||
{
|
||||
m_formatted.clear();
|
||||
for (u32 i = 0; i < m_unformatted.size(); ++i)
|
||||
{
|
||||
if (i == restore_scroll_unformatted)
|
||||
restore_scroll_formatted = m_formatted.size();
|
||||
formatChatLine(m_unformatted[i], cols, m_formatted);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the console size
|
||||
m_cols = cols;
|
||||
m_rows = rows;
|
||||
|
||||
// Restore the scroll position
|
||||
if (at_bottom)
|
||||
{
|
||||
scrollBottom();
|
||||
}
|
||||
else
|
||||
{
|
||||
scrollAbsolute(restore_scroll_formatted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ChatFormattedLine& ChatBuffer::getFormattedLine(u32 row) const
|
||||
{
|
||||
s32 index = m_scroll + (s32) row;
|
||||
if (index >= 0 && index < (s32) m_formatted.size())
|
||||
return m_formatted[index];
|
||||
else
|
||||
return m_empty_formatted_line;
|
||||
}
|
||||
|
||||
void ChatBuffer::scroll(s32 rows)
|
||||
{
|
||||
scrollAbsolute(m_scroll + rows);
|
||||
}
|
||||
|
||||
void ChatBuffer::scrollAbsolute(s32 scroll)
|
||||
{
|
||||
s32 top = getTopScrollPos();
|
||||
s32 bottom = getBottomScrollPos();
|
||||
|
||||
m_scroll = scroll;
|
||||
if (m_scroll < top)
|
||||
m_scroll = top;
|
||||
if (m_scroll > bottom)
|
||||
m_scroll = bottom;
|
||||
}
|
||||
|
||||
void ChatBuffer::scrollBottom()
|
||||
{
|
||||
m_scroll = getBottomScrollPos();
|
||||
}
|
||||
|
||||
void ChatBuffer::scrollTop()
|
||||
{
|
||||
m_scroll = getTopScrollPos();
|
||||
}
|
||||
|
||||
u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols,
|
||||
core::array<ChatFormattedLine>& destination) const
|
||||
{
|
||||
u32 num_added = 0;
|
||||
core::array<ChatFormattedFragment> next_frags;
|
||||
ChatFormattedLine next_line;
|
||||
ChatFormattedFragment temp_frag;
|
||||
u32 out_column = 0;
|
||||
u32 in_pos = 0;
|
||||
u32 hanging_indentation = 0;
|
||||
|
||||
// Format the sender name and produce fragments
|
||||
if (!line.name.empty())
|
||||
{
|
||||
temp_frag.text = L"<";
|
||||
temp_frag.column = 0;
|
||||
//temp_frag.bold = 0;
|
||||
next_frags.push_back(temp_frag);
|
||||
temp_frag.text = line.name;
|
||||
temp_frag.column = 0;
|
||||
//temp_frag.bold = 1;
|
||||
next_frags.push_back(temp_frag);
|
||||
temp_frag.text = L"> ";
|
||||
temp_frag.column = 0;
|
||||
//temp_frag.bold = 0;
|
||||
next_frags.push_back(temp_frag);
|
||||
}
|
||||
|
||||
// Choose an indentation level
|
||||
if (line.name.empty())
|
||||
{
|
||||
// Server messages
|
||||
hanging_indentation = 0;
|
||||
}
|
||||
else if (line.name.size() + 3 <= cols/2)
|
||||
{
|
||||
// Names shorter than about half the console width
|
||||
hanging_indentation = line.name.size() + 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Very long names
|
||||
hanging_indentation = 2;
|
||||
}
|
||||
|
||||
next_line.first = true;
|
||||
bool text_processing = false;
|
||||
|
||||
// Produce fragments and layout them into lines
|
||||
while (!next_frags.empty() || in_pos < line.text.size())
|
||||
{
|
||||
// Layout fragments into lines
|
||||
while (!next_frags.empty())
|
||||
{
|
||||
ChatFormattedFragment& frag = next_frags[0];
|
||||
if (frag.text.size() <= cols - out_column)
|
||||
{
|
||||
// Fragment fits into current line
|
||||
frag.column = out_column;
|
||||
next_line.fragments.push_back(frag);
|
||||
out_column += frag.text.size();
|
||||
next_frags.erase(0, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fragment does not fit into current line
|
||||
// So split it up
|
||||
temp_frag.text = frag.text.substr(0, cols - out_column);
|
||||
temp_frag.column = out_column;
|
||||
//temp_frag.bold = frag.bold;
|
||||
next_line.fragments.push_back(temp_frag);
|
||||
frag.text = frag.text.substr(cols - out_column);
|
||||
out_column = cols;
|
||||
}
|
||||
if (out_column == cols || text_processing)
|
||||
{
|
||||
// End the current line
|
||||
destination.push_back(next_line);
|
||||
num_added++;
|
||||
next_line.fragments.clear();
|
||||
next_line.first = false;
|
||||
|
||||
out_column = text_processing ? hanging_indentation : 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Produce fragment
|
||||
if (in_pos < line.text.size())
|
||||
{
|
||||
u32 remaining_in_input = line.text.size() - in_pos;
|
||||
u32 remaining_in_output = cols - out_column;
|
||||
|
||||
// Determine a fragment length <= the minimum of
|
||||
// remaining_in_{in,out}put. Try to end the fragment
|
||||
// on a word boundary.
|
||||
u32 frag_length = 1, space_pos = 0;
|
||||
while (frag_length < remaining_in_input &&
|
||||
frag_length < remaining_in_output)
|
||||
{
|
||||
if (isspace(line.text[in_pos + frag_length]))
|
||||
space_pos = frag_length;
|
||||
++frag_length;
|
||||
}
|
||||
if (space_pos != 0 && frag_length < remaining_in_input)
|
||||
frag_length = space_pos + 1;
|
||||
|
||||
temp_frag.text = line.text.substr(in_pos, frag_length);
|
||||
temp_frag.column = 0;
|
||||
//temp_frag.bold = 0;
|
||||
next_frags.push_back(temp_frag);
|
||||
in_pos += frag_length;
|
||||
text_processing = true;
|
||||
}
|
||||
}
|
||||
|
||||
// End the last line
|
||||
if (num_added == 0 || !next_line.fragments.empty())
|
||||
{
|
||||
destination.push_back(next_line);
|
||||
num_added++;
|
||||
}
|
||||
|
||||
return num_added;
|
||||
}
|
||||
|
||||
s32 ChatBuffer::getTopScrollPos() const
|
||||
{
|
||||
s32 formatted_count = (s32) m_formatted.size();
|
||||
s32 rows = (s32) m_rows;
|
||||
if (rows == 0)
|
||||
return 0;
|
||||
else if (formatted_count <= rows)
|
||||
return formatted_count - rows;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
s32 ChatBuffer::getBottomScrollPos() const
|
||||
{
|
||||
s32 formatted_count = (s32) m_formatted.size();
|
||||
s32 rows = (s32) m_rows;
|
||||
if (rows == 0)
|
||||
return 0;
|
||||
else
|
||||
return formatted_count - rows;
|
||||
}
|
||||
|
||||
|
||||
|
||||
ChatPrompt::ChatPrompt(std::wstring prompt, u32 history_limit):
|
||||
m_prompt(prompt),
|
||||
m_line(L""),
|
||||
m_history(),
|
||||
m_history_index(0),
|
||||
m_history_limit(history_limit),
|
||||
m_cols(0),
|
||||
m_view(0),
|
||||
m_cursor(0),
|
||||
m_nick_completion_start(0),
|
||||
m_nick_completion_end(0)
|
||||
{
|
||||
}
|
||||
|
||||
ChatPrompt::~ChatPrompt()
|
||||
{
|
||||
}
|
||||
|
||||
void ChatPrompt::input(wchar_t ch)
|
||||
{
|
||||
m_line.insert(m_cursor, 1, ch);
|
||||
m_cursor++;
|
||||
clampView();
|
||||
m_nick_completion_start = 0;
|
||||
m_nick_completion_end = 0;
|
||||
}
|
||||
|
||||
std::wstring ChatPrompt::submit()
|
||||
{
|
||||
std::wstring line = m_line;
|
||||
m_line.clear();
|
||||
if (!line.empty())
|
||||
m_history.push_back(line);
|
||||
if (m_history.size() > m_history_limit)
|
||||
m_history.erase(0);
|
||||
m_history_index = m_history.size();
|
||||
m_view = 0;
|
||||
m_cursor = 0;
|
||||
m_nick_completion_start = 0;
|
||||
m_nick_completion_end = 0;
|
||||
return line;
|
||||
}
|
||||
|
||||
void ChatPrompt::clear()
|
||||
{
|
||||
m_line.clear();
|
||||
m_view = 0;
|
||||
m_cursor = 0;
|
||||
m_nick_completion_start = 0;
|
||||
m_nick_completion_end = 0;
|
||||
}
|
||||
|
||||
void ChatPrompt::replace(std::wstring line)
|
||||
{
|
||||
m_line = line;
|
||||
m_view = m_cursor = line.size();
|
||||
clampView();
|
||||
m_nick_completion_start = 0;
|
||||
m_nick_completion_end = 0;
|
||||
}
|
||||
|
||||
void ChatPrompt::historyPrev()
|
||||
{
|
||||
if (m_history_index != 0)
|
||||
{
|
||||
--m_history_index;
|
||||
replace(m_history[m_history_index]);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatPrompt::historyNext()
|
||||
{
|
||||
if (m_history_index + 1 >= m_history.size())
|
||||
{
|
||||
m_history_index = m_history.size();
|
||||
replace(L"");
|
||||
}
|
||||
else
|
||||
{
|
||||
++m_history_index;
|
||||
replace(m_history[m_history_index]);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatPrompt::nickCompletion(const core::list<std::wstring>& names, bool backwards)
|
||||
{
|
||||
// Two cases:
|
||||
// (a) m_nick_completion_start == m_nick_completion_end == 0
|
||||
// Then no previous nick completion is active.
|
||||
// Get the word around the cursor and replace with any nick
|
||||
// that has that word as a prefix.
|
||||
// (b) else, continue a previous nick completion.
|
||||
// m_nick_completion_start..m_nick_completion_end are the
|
||||
// interval where the originally used prefix was. Cycle
|
||||
// through the list of completions of that prefix.
|
||||
u32 prefix_start = m_nick_completion_start;
|
||||
u32 prefix_end = m_nick_completion_end;
|
||||
bool initial = (prefix_end == 0);
|
||||
if (initial)
|
||||
{
|
||||
// no previous nick completion is active
|
||||
prefix_start = prefix_end = m_cursor;
|
||||
while (prefix_start > 0 && !isspace(m_line[prefix_start-1]))
|
||||
--prefix_start;
|
||||
while (prefix_end < m_line.size() && !isspace(m_line[prefix_end]))
|
||||
++prefix_end;
|
||||
if (prefix_start == prefix_end)
|
||||
return;
|
||||
}
|
||||
std::wstring prefix = m_line.substr(prefix_start, prefix_end - prefix_start);
|
||||
|
||||
// find all names that start with the selected prefix
|
||||
core::array<std::wstring> completions;
|
||||
for (core::list<std::wstring>::ConstIterator
|
||||
i = names.begin();
|
||||
i != names.end(); i++)
|
||||
{
|
||||
if (str_starts_with(*i, prefix, true))
|
||||
{
|
||||
std::wstring completion = *i;
|
||||
if (prefix_start == 0)
|
||||
completion += L":";
|
||||
completions.push_back(completion);
|
||||
}
|
||||
}
|
||||
if (completions.empty())
|
||||
return;
|
||||
|
||||
// find a replacement string and the word that will be replaced
|
||||
u32 word_end = prefix_end;
|
||||
u32 replacement_index = 0;
|
||||
if (!initial)
|
||||
{
|
||||
while (word_end < m_line.size() && !isspace(m_line[word_end]))
|
||||
++word_end;
|
||||
std::wstring word = m_line.substr(prefix_start, word_end - prefix_start);
|
||||
|
||||
// cycle through completions
|
||||
for (u32 i = 0; i < completions.size(); ++i)
|
||||
{
|
||||
if (str_equal(word, completions[i], true))
|
||||
{
|
||||
if (backwards)
|
||||
replacement_index = i + completions.size() - 1;
|
||||
else
|
||||
replacement_index = i + 1;
|
||||
replacement_index %= completions.size();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::wstring replacement = completions[replacement_index] + L" ";
|
||||
if (word_end < m_line.size() && isspace(word_end))
|
||||
++word_end;
|
||||
|
||||
// replace existing word with replacement word,
|
||||
// place the cursor at the end and record the completion prefix
|
||||
m_line.replace(prefix_start, word_end - prefix_start, replacement);
|
||||
m_cursor = prefix_start + replacement.size();
|
||||
clampView();
|
||||
m_nick_completion_start = prefix_start;
|
||||
m_nick_completion_end = prefix_end;
|
||||
}
|
||||
|
||||
void ChatPrompt::reformat(u32 cols)
|
||||
{
|
||||
if (cols <= m_prompt.size())
|
||||
{
|
||||
m_cols = 0;
|
||||
m_view = m_cursor;
|
||||
}
|
||||
else
|
||||
{
|
||||
s32 length = m_line.size();
|
||||
bool was_at_end = (m_view + m_cols >= length + 1);
|
||||
m_cols = cols - m_prompt.size();
|
||||
if (was_at_end)
|
||||
m_view = length;
|
||||
clampView();
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring ChatPrompt::getVisiblePortion() const
|
||||
{
|
||||
return m_prompt + m_line.substr(m_view, m_cols);
|
||||
}
|
||||
|
||||
s32 ChatPrompt::getVisibleCursorPosition() const
|
||||
{
|
||||
return m_cursor - m_view + m_prompt.size();
|
||||
}
|
||||
|
||||
void ChatPrompt::cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope scope)
|
||||
{
|
||||
s32 old_cursor = m_cursor;
|
||||
s32 new_cursor = m_cursor;
|
||||
|
||||
s32 length = m_line.size();
|
||||
s32 increment = (dir == CURSOROP_DIR_RIGHT) ? 1 : -1;
|
||||
|
||||
if (scope == CURSOROP_SCOPE_CHARACTER)
|
||||
{
|
||||
new_cursor += increment;
|
||||
}
|
||||
else if (scope == CURSOROP_SCOPE_WORD)
|
||||
{
|
||||
if (increment > 0)
|
||||
{
|
||||
// skip one word to the right
|
||||
while (new_cursor < length && isspace(m_line[new_cursor]))
|
||||
new_cursor++;
|
||||
while (new_cursor < length && !isspace(m_line[new_cursor]))
|
||||
new_cursor++;
|
||||
while (new_cursor < length && isspace(m_line[new_cursor]))
|
||||
new_cursor++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// skip one word to the left
|
||||
while (new_cursor >= 1 && isspace(m_line[new_cursor - 1]))
|
||||
new_cursor--;
|
||||
while (new_cursor >= 1 && !isspace(m_line[new_cursor - 1]))
|
||||
new_cursor--;
|
||||
}
|
||||
}
|
||||
else if (scope == CURSOROP_SCOPE_LINE)
|
||||
{
|
||||
new_cursor += increment * length;
|
||||
}
|
||||
|
||||
new_cursor = MYMAX(MYMIN(new_cursor, length), 0);
|
||||
|
||||
if (op == CURSOROP_MOVE)
|
||||
{
|
||||
m_cursor = new_cursor;
|
||||
}
|
||||
else if (op == CURSOROP_DELETE)
|
||||
{
|
||||
if (new_cursor < old_cursor)
|
||||
{
|
||||
m_line.erase(new_cursor, old_cursor - new_cursor);
|
||||
m_cursor = new_cursor;
|
||||
}
|
||||
else if (new_cursor > old_cursor)
|
||||
{
|
||||
m_line.erase(old_cursor, new_cursor - old_cursor);
|
||||
m_cursor = old_cursor;
|
||||
}
|
||||
}
|
||||
|
||||
clampView();
|
||||
|
||||
m_nick_completion_start = 0;
|
||||
m_nick_completion_end = 0;
|
||||
}
|
||||
|
||||
void ChatPrompt::clampView()
|
||||
{
|
||||
s32 length = m_line.size();
|
||||
if (length + 1 <= m_cols)
|
||||
{
|
||||
m_view = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_view = MYMIN(m_view, length + 1 - m_cols);
|
||||
m_view = MYMIN(m_view, m_cursor);
|
||||
m_view = MYMAX(m_view, m_cursor - m_cols + 1);
|
||||
m_view = MYMAX(m_view, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
ChatBackend::ChatBackend():
|
||||
m_console_buffer(500),
|
||||
m_recent_buffer(6),
|
||||
m_prompt(L"]", 500)
|
||||
{
|
||||
}
|
||||
|
||||
ChatBackend::~ChatBackend()
|
||||
{
|
||||
}
|
||||
|
||||
void ChatBackend::addMessage(std::wstring name, std::wstring text)
|
||||
{
|
||||
// Note: A message may consist of multiple lines, for example the MOTD.
|
||||
WStrfnd fnd(text);
|
||||
while (!fnd.atend())
|
||||
{
|
||||
std::wstring line = fnd.next(L"\n");
|
||||
m_console_buffer.addLine(name, line);
|
||||
m_recent_buffer.addLine(name, line);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatBackend::addUnparsedMessage(std::wstring message)
|
||||
{
|
||||
// TODO: Remove the need to parse chat messages client-side, by sending
|
||||
// separate name and text fields in TOCLIENT_CHAT_MESSAGE.
|
||||
|
||||
if (message.size() >= 2 && message[0] == L'<')
|
||||
{
|
||||
std::size_t closing = message.find_first_of(L'>', 1);
|
||||
if (closing != std::wstring::npos &&
|
||||
closing + 2 <= message.size() &&
|
||||
message[closing+1] == L' ')
|
||||
{
|
||||
std::wstring name = message.substr(1, closing - 1);
|
||||
std::wstring text = message.substr(closing + 2);
|
||||
addMessage(name, text);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Unable to parse, probably a server message.
|
||||
addMessage(L"", message);
|
||||
}
|
||||
|
||||
ChatBuffer& ChatBackend::getConsoleBuffer()
|
||||
{
|
||||
return m_console_buffer;
|
||||
}
|
||||
|
||||
ChatBuffer& ChatBackend::getRecentBuffer()
|
||||
{
|
||||
return m_recent_buffer;
|
||||
}
|
||||
|
||||
std::wstring ChatBackend::getRecentChat()
|
||||
{
|
||||
std::wostringstream stream;
|
||||
for (u32 i = 0; i < m_recent_buffer.getLineCount(); ++i)
|
||||
{
|
||||
const ChatLine& line = m_recent_buffer.getLine(i);
|
||||
if (i != 0)
|
||||
stream << L"\n";
|
||||
if (!line.name.empty())
|
||||
stream << L"<" << line.name << L"> ";
|
||||
stream << line.text;
|
||||
}
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
ChatPrompt& ChatBackend::getPrompt()
|
||||
{
|
||||
return m_prompt;
|
||||
}
|
||||
|
||||
void ChatBackend::reformat(u32 cols, u32 rows)
|
||||
{
|
||||
m_console_buffer.reformat(cols, rows);
|
||||
|
||||
// no need to reformat m_recent_buffer, its formatted lines
|
||||
// are not used
|
||||
|
||||
m_prompt.reformat(cols);
|
||||
}
|
||||
|
||||
void ChatBackend::clearRecentChat()
|
||||
{
|
||||
m_recent_buffer.clear();
|
||||
}
|
||||
|
||||
void ChatBackend::step(float dtime)
|
||||
{
|
||||
m_recent_buffer.step(dtime);
|
||||
m_recent_buffer.deleteByAge(60.0);
|
||||
|
||||
// no need to age messages in anything but m_recent_buffer
|
||||
}
|
||||
|
||||
void ChatBackend::scroll(s32 rows)
|
||||
{
|
||||
m_console_buffer.scroll(rows);
|
||||
}
|
||||
|
||||
void ChatBackend::scrollPageDown()
|
||||
{
|
||||
m_console_buffer.scroll(m_console_buffer.getRows());
|
||||
}
|
||||
|
||||
void ChatBackend::scrollPageUp()
|
||||
{
|
||||
m_console_buffer.scroll(-m_console_buffer.getRows());
|
||||
}
|
272
src/chat.h
Normal file
272
src/chat.h
Normal file
@ -0,0 +1,272 @@
|
||||
/*
|
||||
Minetest-c55
|
||||
Copyright (C) 2011 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||
|
||||
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 2 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, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef CHAT_HEADER
|
||||
#define CHAT_HEADER
|
||||
|
||||
#include "common_irrlicht.h"
|
||||
#include <string>
|
||||
|
||||
// Chat console related classes, only used by the client
|
||||
|
||||
struct ChatLine
|
||||
{
|
||||
// age in seconds
|
||||
f32 age;
|
||||
// name of sending player, or empty if sent by server
|
||||
std::wstring name;
|
||||
// message text
|
||||
std::wstring text;
|
||||
|
||||
ChatLine(std::wstring a_name, std::wstring a_text):
|
||||
age(0.0),
|
||||
name(a_name),
|
||||
text(a_text)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct ChatFormattedFragment
|
||||
{
|
||||
// text string
|
||||
std::wstring text;
|
||||
// starting column
|
||||
u32 column;
|
||||
// formatting
|
||||
//u8 bold:1;
|
||||
};
|
||||
|
||||
struct ChatFormattedLine
|
||||
{
|
||||
// Array of text fragments
|
||||
core::array<ChatFormattedFragment> fragments;
|
||||
// true if first line of one formatted ChatLine
|
||||
bool first;
|
||||
};
|
||||
|
||||
class ChatBuffer
|
||||
{
|
||||
public:
|
||||
ChatBuffer(u32 scrollback);
|
||||
~ChatBuffer();
|
||||
|
||||
// Append chat line
|
||||
// Removes oldest chat line if scrollback size is reached
|
||||
void addLine(std::wstring name, std::wstring text);
|
||||
|
||||
// Remove all chat lines
|
||||
void clear();
|
||||
|
||||
// Get number of lines currently in buffer.
|
||||
u32 getLineCount() const;
|
||||
// Get scrollback size, maximum number of lines in buffer.
|
||||
u32 getScrollback() const;
|
||||
// Get reference to i-th chat line.
|
||||
const ChatLine& getLine(u32 index) const;
|
||||
|
||||
// Increase each chat line's age by dtime.
|
||||
void step(f32 dtime);
|
||||
// Delete oldest N chat lines.
|
||||
void deleteOldest(u32 count);
|
||||
// Delete lines older than maxAge.
|
||||
void deleteByAge(f32 maxAge);
|
||||
|
||||
// Get number of columns, 0 if reformat has not been called yet.
|
||||
u32 getColumns() const;
|
||||
// Get number of rows, 0 if reformat has not been called yet.
|
||||
u32 getRows() const;
|
||||
// Update console size and reformat all formatted lines.
|
||||
void reformat(u32 cols, u32 rows);
|
||||
// Get formatted line for a given row (0 is top of screen).
|
||||
// Only valid after reformat has been called at least once
|
||||
const ChatFormattedLine& getFormattedLine(u32 row) const;
|
||||
// Scrolling in formatted buffer (relative)
|
||||
// positive rows == scroll up, negative rows == scroll down
|
||||
void scroll(s32 rows);
|
||||
// Scrolling in formatted buffer (absolute)
|
||||
void scrollAbsolute(s32 scroll);
|
||||
// Scroll to bottom of buffer (newest)
|
||||
void scrollBottom();
|
||||
// Scroll to top of buffer (oldest)
|
||||
void scrollTop();
|
||||
|
||||
// Format a chat line for the given number of columns.
|
||||
// Appends the formatted lines to the destination array and
|
||||
// returns the number of formatted lines.
|
||||
u32 formatChatLine(const ChatLine& line, u32 cols,
|
||||
core::array<ChatFormattedLine>& destination) const;
|
||||
|
||||
protected:
|
||||
s32 getTopScrollPos() const;
|
||||
s32 getBottomScrollPos() const;
|
||||
|
||||
private:
|
||||
// Scrollback size
|
||||
u32 m_scrollback;
|
||||
// Array of unformatted chat lines
|
||||
core::array<ChatLine> m_unformatted;
|
||||
|
||||
// Number of character columns in console
|
||||
u32 m_cols;
|
||||
// Number of character rows in console
|
||||
u32 m_rows;
|
||||
// Scroll position (console's top line index into m_formatted)
|
||||
s32 m_scroll;
|
||||
// Array of formatted lines
|
||||
core::array<ChatFormattedLine> m_formatted;
|
||||
// Empty formatted line, for error returns
|
||||
ChatFormattedLine m_empty_formatted_line;
|
||||
};
|
||||
|
||||
class ChatPrompt
|
||||
{
|
||||
public:
|
||||
ChatPrompt(std::wstring prompt, u32 history_limit);
|
||||
~ChatPrompt();
|
||||
|
||||
// Input character
|
||||
void input(wchar_t ch);
|
||||
|
||||
// Submit, clear and return current line
|
||||
std::wstring submit();
|
||||
|
||||
// Clear the current line
|
||||
void clear();
|
||||
|
||||
// Replace the current line with the given text
|
||||
void replace(std::wstring line);
|
||||
|
||||
// Select previous command from history
|
||||
void historyPrev();
|
||||
// Select next command from history
|
||||
void historyNext();
|
||||
|
||||
// Nick completion
|
||||
void nickCompletion(const core::list<std::wstring>& names, bool backwards);
|
||||
|
||||
// Update console size and reformat the visible portion of the prompt
|
||||
void reformat(u32 cols);
|
||||
// Get visible portion of the prompt.
|
||||
std::wstring getVisiblePortion() const;
|
||||
// Get cursor position (relative to visible portion). -1 if invalid
|
||||
s32 getVisibleCursorPosition() const;
|
||||
|
||||
// Cursor operations
|
||||
enum CursorOp {
|
||||
CURSOROP_MOVE,
|
||||
CURSOROP_DELETE
|
||||
};
|
||||
|
||||
// Cursor operation direction
|
||||
enum CursorOpDir {
|
||||
CURSOROP_DIR_LEFT,
|
||||
CURSOROP_DIR_RIGHT
|
||||
};
|
||||
|
||||
// Cursor operation scope
|
||||
enum CursorOpScope {
|
||||
CURSOROP_SCOPE_CHARACTER,
|
||||
CURSOROP_SCOPE_WORD,
|
||||
CURSOROP_SCOPE_LINE
|
||||
};
|
||||
|
||||
// Cursor operation
|
||||
// op specifies whether it's a move or delete operation
|
||||
// dir specifies whether the operation goes left or right
|
||||
// scope specifies how far the operation will reach (char/word/line)
|
||||
// Examples:
|
||||
// cursorOperation(CURSOROP_MOVE, CURSOROP_DIR_RIGHT, CURSOROP_SCOPE_LINE)
|
||||
// moves the cursor to the end of the line.
|
||||
// cursorOperation(CURSOROP_DELETE, CURSOROP_DIR_LEFT, CURSOROP_SCOPE_WORD)
|
||||
// deletes the word to the left of the cursor.
|
||||
void cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope scope);
|
||||
|
||||
protected:
|
||||
// set m_view to ensure that 0 <= m_view <= m_cursor < m_view + m_cols
|
||||
// if line can be fully shown, set m_view to zero
|
||||
// else, also ensure m_view <= m_line.size() + 1 - m_cols
|
||||
void clampView();
|
||||
|
||||
private:
|
||||
// Prompt prefix
|
||||
std::wstring m_prompt;
|
||||
// Currently edited line
|
||||
std::wstring m_line;
|
||||
// History buffer
|
||||
core::array<std::wstring> m_history;
|
||||
// History index (0 <= m_history_index <= m_history.size())
|
||||
u32 m_history_index;
|
||||
// Maximum number of history entries
|
||||
u32 m_history_limit;
|
||||
|
||||
// Number of columns excluding columns reserved for the prompt
|
||||
s32 m_cols;
|
||||
// Start of visible portion (index into m_line)
|
||||
s32 m_view;
|
||||
// Cursor (index into m_line)
|
||||
s32 m_cursor;
|
||||
|
||||
// Last nick completion start (index into m_line)
|
||||
s32 m_nick_completion_start;
|
||||
// Last nick completion start (index into m_line)
|
||||
s32 m_nick_completion_end;
|
||||
};
|
||||
|
||||
class ChatBackend
|
||||
{
|
||||
public:
|
||||
ChatBackend();
|
||||
~ChatBackend();
|
||||
|
||||
// Add chat message
|
||||
void addMessage(std::wstring name, std::wstring text);
|
||||
// Parse and add unparsed chat message
|
||||
void addUnparsedMessage(std::wstring line);
|
||||
|
||||
// Get the console buffer
|
||||
ChatBuffer& getConsoleBuffer();
|
||||
// Get the recent messages buffer
|
||||
ChatBuffer& getRecentBuffer();
|
||||
// Concatenate all recent messages
|
||||
std::wstring getRecentChat();
|
||||
// Get the console prompt
|
||||
ChatPrompt& getPrompt();
|
||||
|
||||
// Reformat all buffers
|
||||
void reformat(u32 cols, u32 rows);
|
||||
|
||||
// Clear all recent messages
|
||||
void clearRecentChat();
|
||||
|
||||
// Age recent messages
|
||||
void step(float dtime);
|
||||
|
||||
// Scrolling
|
||||
void scroll(s32 rows);
|
||||
void scrollPageDown();
|
||||
void scrollPageUp();
|
||||
|
||||
private:
|
||||
ChatBuffer m_console_buffer;
|
||||
ChatBuffer m_recent_buffer;
|
||||
ChatPrompt m_prompt;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -2036,7 +2036,21 @@ void Client::printDebugInfo(std::ostream &os)
|
||||
//<<", m_opt_not_found_history.size()="<<m_opt_not_found_history.size()
|
||||
<<std::endl;*/
|
||||
}
|
||||
|
||||
|
||||
core::list<std::wstring> Client::getConnectedPlayerNames()
|
||||
{
|
||||
core::list<Player*> players = m_env.getPlayers(true);
|
||||
core::list<std::wstring> playerNames;
|
||||
for(core::list<Player*>::Iterator
|
||||
i = players.begin();
|
||||
i != players.end(); i++)
|
||||
{
|
||||
Player *player = *i;
|
||||
playerNames.push_back(narrow_to_wide(player->getName()));
|
||||
}
|
||||
return playerNames;
|
||||
}
|
||||
|
||||
u32 Client::getDayNightRatio()
|
||||
{
|
||||
//JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
|
||||
@ -2084,6 +2098,39 @@ void Client::clearTempMod(v3s16 p)
|
||||
}
|
||||
}
|
||||
|
||||
bool Client::getChatMessage(std::wstring &message)
|
||||
{
|
||||
if(m_chat_queue.size() == 0)
|
||||
return false;
|
||||
message = m_chat_queue.pop_front();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Client::typeChatMessage(const std::wstring &message)
|
||||
{
|
||||
// Discard empty line
|
||||
if(message == L"")
|
||||
return;
|
||||
|
||||
// Send to others
|
||||
sendChatMessage(message);
|
||||
|
||||
// Show locally
|
||||
if (message[0] == L'/')
|
||||
{
|
||||
m_chat_queue.push_back(
|
||||
(std::wstring)L"issued command: "+message);
|
||||
}
|
||||
else
|
||||
{
|
||||
LocalPlayer *player = m_env.getLocalPlayer();
|
||||
assert(player != NULL);
|
||||
std::wstring name = narrow_to_wide(player->getName());
|
||||
m_chat_queue.push_back(
|
||||
(std::wstring)L"<"+name+L"> "+message);
|
||||
}
|
||||
}
|
||||
|
||||
void Client::addUpdateMeshTask(v3s16 p, bool ack_to_server)
|
||||
{
|
||||
/*infostream<<"Client::addUpdateMeshTask(): "
|
||||
|
27
src/client.h
27
src/client.h
@ -258,6 +258,8 @@ public:
|
||||
// Prints a line or two of info
|
||||
void printDebugInfo(std::ostream &os);
|
||||
|
||||
core::list<std::wstring> getConnectedPlayerNames();
|
||||
|
||||
u32 getDayNightRatio();
|
||||
|
||||
u16 getHP();
|
||||
@ -274,29 +276,8 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
bool getChatMessage(std::wstring &message)
|
||||
{
|
||||
if(m_chat_queue.size() == 0)
|
||||
return false;
|
||||
message = m_chat_queue.pop_front();
|
||||
return true;
|
||||
}
|
||||
|
||||
void addChatMessage(const std::wstring &message)
|
||||
{
|
||||
if (message[0] == L'/') {
|
||||
m_chat_queue.push_back(
|
||||
(std::wstring)L"issued command: "+message);
|
||||
return;
|
||||
}
|
||||
|
||||
//JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
|
||||
LocalPlayer *player = m_env.getLocalPlayer();
|
||||
assert(player != NULL);
|
||||
std::wstring name = narrow_to_wide(player->getName());
|
||||
m_chat_queue.push_back(
|
||||
(std::wstring)L"<"+name+L"> "+message);
|
||||
}
|
||||
bool getChatMessage(std::wstring &message);
|
||||
void typeChatMessage(const std::wstring& message);
|
||||
|
||||
u64 getMapSeed(){ return m_map_seed; }
|
||||
|
||||
|
@ -40,6 +40,7 @@ void set_default_settings(Settings *settings)
|
||||
settings->setDefault("keymap_special1", "KEY_KEY_E");
|
||||
settings->setDefault("keymap_chat", "KEY_KEY_T");
|
||||
settings->setDefault("keymap_cmd", "/");
|
||||
settings->setDefault("keymap_console", "KEY_F10");
|
||||
settings->setDefault("keymap_rangeselect", "KEY_KEY_R");
|
||||
settings->setDefault("keymap_freemove", "KEY_KEY_K");
|
||||
settings->setDefault("keymap_fastmove", "KEY_KEY_J");
|
||||
@ -91,7 +92,8 @@ void set_default_settings(Settings *settings)
|
||||
settings->setDefault("view_bobbing_amount", "1.0");
|
||||
settings->setDefault("enable_3d_clouds", "false");
|
||||
settings->setDefault("opaque_water", "false");
|
||||
|
||||
settings->setDefault("console_color", "(0,0,0)");
|
||||
settings->setDefault("console_alpha", "200");
|
||||
// Server stuff
|
||||
// "map-dir" doesn't exist by default.
|
||||
settings->setDefault("motd", "");
|
||||
|
149
src/game.cpp
149
src/game.cpp
@ -32,6 +32,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#include "guiTextInputMenu.h"
|
||||
#include "guiDeathScreen.h"
|
||||
#include "tool.h"
|
||||
#include "guiChatConsole.h"
|
||||
#include "config.h"
|
||||
#include "clouds.h"
|
||||
#include "camera.h"
|
||||
@ -62,22 +63,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#define FIELD_OF_VIEW_TEST 0
|
||||
|
||||
|
||||
// Chat data
|
||||
struct ChatLine
|
||||
{
|
||||
ChatLine():
|
||||
age(0.0)
|
||||
{
|
||||
}
|
||||
ChatLine(const std::wstring &a_text):
|
||||
age(0.0),
|
||||
text(a_text)
|
||||
{
|
||||
}
|
||||
float age;
|
||||
std::wstring text;
|
||||
};
|
||||
|
||||
/*
|
||||
Text input system
|
||||
*/
|
||||
@ -90,14 +75,7 @@ struct TextDestChat : public TextDest
|
||||
}
|
||||
void gotText(std::wstring text)
|
||||
{
|
||||
// Discard empty line
|
||||
if(text == L"")
|
||||
return;
|
||||
|
||||
// Send to others
|
||||
m_client->sendChatMessage(text);
|
||||
// Show locally
|
||||
m_client->addChatMessage(text);
|
||||
m_client->typeChatMessage(text);
|
||||
}
|
||||
|
||||
Client *m_client;
|
||||
@ -676,7 +654,8 @@ void the_game(
|
||||
std::string address,
|
||||
u16 port,
|
||||
std::wstring &error_message,
|
||||
std::string configpath
|
||||
std::string configpath,
|
||||
ChatBackend &chat_backend
|
||||
)
|
||||
{
|
||||
video::IVideoDriver* driver = device->getVideoDriver();
|
||||
@ -978,8 +957,10 @@ void the_game(
|
||||
core::rect<s32>(0,0,0,0),
|
||||
//false, false); // Disable word wrap as of now
|
||||
false, true);
|
||||
//guitext_chat->setBackgroundColor(video::SColor(96,0,0,0));
|
||||
core::list<ChatLine> chat_lines;
|
||||
// Remove stale "recent" chat messages from previous connections
|
||||
chat_backend.clearRecentChat();
|
||||
// Chat backend and console
|
||||
GUIChatConsole *gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(), -1, &chat_backend, &client);
|
||||
|
||||
// Profiler text (size is updated when text is updated)
|
||||
gui::IGUIStaticText *guitext_profiler = guienv->addStaticText(
|
||||
@ -1299,7 +1280,9 @@ void the_game(
|
||||
*/
|
||||
|
||||
// Reset input if window not active or some menu is active
|
||||
if(device->isWindowActive() == false || noMenuActive() == false)
|
||||
if(device->isWindowActive() == false
|
||||
|| noMenuActive() == false
|
||||
|| guienv->hasFocus(gui_chat_console))
|
||||
{
|
||||
input->clear();
|
||||
}
|
||||
@ -1375,6 +1358,15 @@ void the_game(
|
||||
&g_menumgr, dest,
|
||||
L"/"))->drop();
|
||||
}
|
||||
else if(input->wasKeyDown(getKeySetting("keymap_console")))
|
||||
{
|
||||
if (!gui_chat_console->isOpenInhibited())
|
||||
{
|
||||
// Open up to over half of the screen
|
||||
gui_chat_console->openConsole(0.6);
|
||||
guienv->setFocus(gui_chat_console);
|
||||
}
|
||||
}
|
||||
else if(input->wasKeyDown(getKeySetting("keymap_freemove")))
|
||||
{
|
||||
if(g_settings->getBool("free_move"))
|
||||
@ -1655,23 +1647,6 @@ void the_game(
|
||||
/*
|
||||
Player speed control
|
||||
*/
|
||||
|
||||
if(!noMenuActive() || !device->isWindowActive())
|
||||
{
|
||||
PlayerControl control(
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
camera_pitch,
|
||||
camera_yaw
|
||||
);
|
||||
client.setPlayerControl(control);
|
||||
}
|
||||
else
|
||||
{
|
||||
/*bool a_up,
|
||||
bool a_down,
|
||||
@ -1758,6 +1733,8 @@ void the_game(
|
||||
&g_menumgr, respawner);
|
||||
menu->drop();
|
||||
|
||||
chat_backend.addMessage(L"", L"You died.");
|
||||
|
||||
/* Handle visualization */
|
||||
|
||||
damage_flash_timer = 0;
|
||||
@ -2357,83 +2334,38 @@ void the_game(
|
||||
// Get new messages from error log buffer
|
||||
while(!chat_log_error_buf.empty())
|
||||
{
|
||||
chat_lines.push_back(ChatLine(narrow_to_wide(
|
||||
chat_log_error_buf.get())));
|
||||
chat_backend.addMessage(L"", narrow_to_wide(
|
||||
chat_log_error_buf.get()));
|
||||
}
|
||||
// Get new messages from client
|
||||
std::wstring message;
|
||||
while(client.getChatMessage(message))
|
||||
{
|
||||
chat_lines.push_back(ChatLine(message));
|
||||
/*if(chat_lines.size() > 6)
|
||||
{
|
||||
core::list<ChatLine>::Iterator
|
||||
i = chat_lines.begin();
|
||||
chat_lines.erase(i);
|
||||
}*/
|
||||
chat_backend.addUnparsedMessage(message);
|
||||
}
|
||||
// Append them to form the whole static text and throw
|
||||
// it to the gui element
|
||||
std::wstring whole;
|
||||
// This will correspond to the line number counted from
|
||||
// top to bottom, from size-1 to 0
|
||||
s16 line_number = chat_lines.size();
|
||||
// Count of messages to be removed from the top
|
||||
u16 to_be_removed_count = 0;
|
||||
for(core::list<ChatLine>::Iterator
|
||||
i = chat_lines.begin();
|
||||
i != chat_lines.end(); i++)
|
||||
{
|
||||
// After this, line number is valid for this loop
|
||||
line_number--;
|
||||
// Increment age
|
||||
(*i).age += dtime;
|
||||
/*
|
||||
This results in a maximum age of 60*6 to the
|
||||
lowermost line and a maximum of 6 lines
|
||||
*/
|
||||
float allowed_age = (6-line_number) * 60.0;
|
||||
// Remove old messages
|
||||
chat_backend.step(dtime);
|
||||
|
||||
if((*i).age > allowed_age)
|
||||
{
|
||||
to_be_removed_count++;
|
||||
continue;
|
||||
}
|
||||
whole += (*i).text + L'\n';
|
||||
}
|
||||
for(u16 i=0; i<to_be_removed_count; i++)
|
||||
{
|
||||
core::list<ChatLine>::Iterator
|
||||
it = chat_lines.begin();
|
||||
chat_lines.erase(it);
|
||||
}
|
||||
guitext_chat->setText(whole.c_str());
|
||||
// Display all messages in a static text element
|
||||
u32 recent_chat_count = chat_backend.getRecentBuffer().getLineCount();
|
||||
std::wstring recent_chat = chat_backend.getRecentChat();
|
||||
guitext_chat->setText(recent_chat.c_str());
|
||||
|
||||
// Update gui element size and position
|
||||
|
||||
/*core::rect<s32> rect(
|
||||
10,
|
||||
screensize.Y - guitext_chat_pad_bottom
|
||||
- text_height*chat_lines.size(),
|
||||
screensize.X - 10,
|
||||
screensize.Y - guitext_chat_pad_bottom
|
||||
);*/
|
||||
|
||||
s32 chat_y = 5+(text_height+5);
|
||||
if(show_debug)
|
||||
chat_y += (text_height+5);
|
||||
core::rect<s32> rect(
|
||||
10,
|
||||
chat_y,
|
||||
screensize.X - 10,
|
||||
chat_y + guitext_chat->getTextHeight()
|
||||
10,
|
||||
chat_y,
|
||||
screensize.X - 10,
|
||||
chat_y + guitext_chat->getTextHeight()
|
||||
);
|
||||
|
||||
guitext_chat->setRelativePosition(rect);
|
||||
|
||||
// Don't show chat if empty or profiler or debug is enabled
|
||||
guitext_chat->setVisible(chat_lines.size() != 0
|
||||
&& show_chat && show_profiler == 0);
|
||||
// Don't show chat if disabled or empty or profiler is enabled
|
||||
guitext_chat->setVisible(show_chat && recent_chat_count != 0
|
||||
&& !show_profiler);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -2634,6 +2566,8 @@ void the_game(
|
||||
*/
|
||||
if(clouds)
|
||||
clouds->drop();
|
||||
if(gui_chat_console)
|
||||
gui_chat_console->drop();
|
||||
|
||||
/*
|
||||
Draw a "shutting down" screen, which will be shown while the map
|
||||
@ -2648,6 +2582,9 @@ void the_game(
|
||||
gui_shuttingdowntext->remove();*/
|
||||
}
|
||||
|
||||
chat_backend.addMessage(L"", L"# Disconnected.");
|
||||
chat_backend.addMessage(L"", L"");
|
||||
|
||||
} // Client scope (must be destructed before destructing *def and tsrc
|
||||
|
||||
delete tsrc;
|
||||
|
@ -122,6 +122,8 @@ public:
|
||||
virtual void clear() {};
|
||||
};
|
||||
|
||||
class ChatBackend; /* to avoid having to include chat.h */
|
||||
|
||||
void the_game(
|
||||
bool &kill,
|
||||
bool random_input,
|
||||
@ -134,7 +136,8 @@ void the_game(
|
||||
std::string address,
|
||||
u16 port,
|
||||
std::wstring &error_message,
|
||||
std::string configpath
|
||||
std::string configpath,
|
||||
ChatBackend &chat_backend
|
||||
);
|
||||
|
||||
#endif
|
||||
|
550
src/guiChatConsole.cpp
Normal file
550
src/guiChatConsole.cpp
Normal file
@ -0,0 +1,550 @@
|
||||
/*
|
||||
Minetest-c55
|
||||
Copyright (C) 2011 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||
|
||||
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 2 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, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "guiChatConsole.h"
|
||||
#include "chat.h"
|
||||
#include "client.h"
|
||||
#include "debug.h"
|
||||
#include "gettime.h"
|
||||
#include "keycode.h"
|
||||
#include "settings.h"
|
||||
#include "main.h" // for g_settings
|
||||
#include "porting.h"
|
||||
#include "tile.h"
|
||||
#include "IGUIFont.h"
|
||||
#include <string>
|
||||
|
||||
#include "gettext.h"
|
||||
|
||||
inline u32 clamp_u8(s32 value)
|
||||
{
|
||||
return (u32) MYMIN(MYMAX(value, 0), 255);
|
||||
}
|
||||
|
||||
|
||||
GUIChatConsole::GUIChatConsole(
|
||||
gui::IGUIEnvironment* env,
|
||||
gui::IGUIElement* parent,
|
||||
s32 id,
|
||||
ChatBackend* backend,
|
||||
Client* client
|
||||
):
|
||||
IGUIElement(gui::EGUIET_ELEMENT, env, parent, id,
|
||||
core::rect<s32>(0,0,100,100)),
|
||||
m_chat_backend(backend),
|
||||
m_client(client),
|
||||
m_screensize(v2u32(0,0)),
|
||||
m_animate_time_old(0),
|
||||
m_open(false),
|
||||
m_height(0),
|
||||
m_desired_height(0),
|
||||
m_desired_height_fraction(0.0),
|
||||
m_height_speed(5.0),
|
||||
m_open_inhibited(0),
|
||||
m_cursor_blink(0.0),
|
||||
m_cursor_blink_speed(0.0),
|
||||
m_cursor_height(0.0),
|
||||
m_background(NULL),
|
||||
m_background_color(255, 0, 0, 0),
|
||||
m_font(NULL),
|
||||
m_fontsize(0, 0)
|
||||
{
|
||||
m_animate_time_old = getTimeMs();
|
||||
|
||||
// load background settings
|
||||
bool console_color_set = !g_settings->get("console_color").empty();
|
||||
s32 console_alpha = g_settings->getS32("console_alpha");
|
||||
|
||||
// load the background texture depending on settings
|
||||
m_background_color.setAlpha(clamp_u8(console_alpha));
|
||||
if (console_color_set)
|
||||
{
|
||||
v3f console_color = g_settings->getV3F("console_color");
|
||||
m_background_color.setRed(clamp_u8(myround(console_color.X)));
|
||||
m_background_color.setGreen(clamp_u8(myround(console_color.Y)));
|
||||
m_background_color.setBlue(clamp_u8(myround(console_color.Z)));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_background = env->getVideoDriver()->getTexture(getTexturePath("background_chat.jpg").c_str());
|
||||
m_background_color.setRed(255);
|
||||
m_background_color.setGreen(255);
|
||||
m_background_color.setBlue(255);
|
||||
}
|
||||
|
||||
// load the font
|
||||
// FIXME should a custom texture_path be searched too?
|
||||
std::string font_name = "fontdejavusansmono.png";
|
||||
m_font = env->getFont(getTexturePath(font_name).c_str());
|
||||
if (m_font == NULL)
|
||||
{
|
||||
dstream << "Unable to load font: " << font_name << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
core::dimension2d<u32> dim = m_font->getDimension(L"M");
|
||||
m_fontsize = v2u32(dim.Width, dim.Height);
|
||||
dstream << "Font size: " << m_fontsize.X << " " << m_fontsize.Y << std::endl;
|
||||
}
|
||||
m_fontsize.X = MYMAX(m_fontsize.X, 1);
|
||||
m_fontsize.Y = MYMAX(m_fontsize.Y, 1);
|
||||
|
||||
// set default cursor options
|
||||
setCursor(true, true, 2.0, 0.1);
|
||||
}
|
||||
|
||||
GUIChatConsole::~GUIChatConsole()
|
||||
{
|
||||
}
|
||||
|
||||
void GUIChatConsole::openConsole(f32 height)
|
||||
{
|
||||
m_open = true;
|
||||
m_desired_height_fraction = height;
|
||||
m_desired_height = height * m_screensize.Y;
|
||||
reformatConsole();
|
||||
}
|
||||
|
||||
bool GUIChatConsole::isOpenInhibited() const
|
||||
{
|
||||
return m_open_inhibited > 0;
|
||||
}
|
||||
|
||||
void GUIChatConsole::closeConsole()
|
||||
{
|
||||
m_open = false;
|
||||
}
|
||||
|
||||
void GUIChatConsole::closeConsoleAtOnce()
|
||||
{
|
||||
m_open = false;
|
||||
m_height = 0;
|
||||
recalculateConsolePosition();
|
||||
}
|
||||
|
||||
f32 GUIChatConsole::getDesiredHeight() const
|
||||
{
|
||||
return m_desired_height_fraction;
|
||||
}
|
||||
|
||||
void GUIChatConsole::setCursor(
|
||||
bool visible, bool blinking, f32 blink_speed, f32 relative_height)
|
||||
{
|
||||
if (visible)
|
||||
{
|
||||
if (blinking)
|
||||
{
|
||||
// leave m_cursor_blink unchanged
|
||||
m_cursor_blink_speed = blink_speed;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_cursor_blink = 0x8000; // on
|
||||
m_cursor_blink_speed = 0.0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_cursor_blink = 0; // off
|
||||
m_cursor_blink_speed = 0.0;
|
||||
}
|
||||
m_cursor_height = relative_height;
|
||||
}
|
||||
|
||||
void GUIChatConsole::draw()
|
||||
{
|
||||
if(!IsVisible)
|
||||
return;
|
||||
|
||||
video::IVideoDriver* driver = Environment->getVideoDriver();
|
||||
|
||||
// Check screen size
|
||||
v2u32 screensize = driver->getScreenSize();
|
||||
if (screensize != m_screensize)
|
||||
{
|
||||
// screen size has changed
|
||||
// scale current console height to new window size
|
||||
if (m_screensize.Y != 0)
|
||||
m_height = m_height * screensize.Y / m_screensize.Y;
|
||||
m_desired_height = m_desired_height_fraction * m_screensize.Y;
|
||||
m_screensize = screensize;
|
||||
reformatConsole();
|
||||
}
|
||||
|
||||
// Animation
|
||||
u32 now = getTimeMs();
|
||||
animate(now - m_animate_time_old);
|
||||
m_animate_time_old = now;
|
||||
|
||||
// Draw console elements if visible
|
||||
if (m_height > 0)
|
||||
{
|
||||
drawBackground();
|
||||
drawText();
|
||||
drawPrompt();
|
||||
}
|
||||
|
||||
gui::IGUIElement::draw();
|
||||
}
|
||||
|
||||
void GUIChatConsole::reformatConsole()
|
||||
{
|
||||
s32 cols = m_screensize.X / m_fontsize.X - 2; // make room for a margin (looks better)
|
||||
s32 rows = m_desired_height / m_fontsize.Y - 1; // make room for the input prompt
|
||||
if (cols <= 0 || rows <= 0)
|
||||
cols = rows = 0;
|
||||
m_chat_backend->reformat(cols, rows);
|
||||
}
|
||||
|
||||
void GUIChatConsole::recalculateConsolePosition()
|
||||
{
|
||||
core::rect<s32> rect(0, 0, m_screensize.X, m_height);
|
||||
DesiredRect = rect;
|
||||
recalculateAbsolutePosition(false);
|
||||
}
|
||||
|
||||
void GUIChatConsole::animate(u32 msec)
|
||||
{
|
||||
// animate the console height
|
||||
s32 goal = m_open ? m_desired_height : 0;
|
||||
if (m_height != goal)
|
||||
{
|
||||
s32 max_change = msec * m_screensize.Y * (m_height_speed / 1000.0);
|
||||
if (max_change == 0)
|
||||
max_change = 1;
|
||||
|
||||
if (m_height < goal)
|
||||
{
|
||||
// increase height
|
||||
if (m_height + max_change < goal)
|
||||
m_height += max_change;
|
||||
else
|
||||
m_height = goal;
|
||||
}
|
||||
else
|
||||
{
|
||||
// decrease height
|
||||
if (m_height > goal + max_change)
|
||||
m_height -= max_change;
|
||||
else
|
||||
m_height = goal;
|
||||
}
|
||||
|
||||
recalculateConsolePosition();
|
||||
}
|
||||
|
||||
// blink the cursor
|
||||
if (m_cursor_blink_speed != 0.0)
|
||||
{
|
||||
u32 blink_increase = 0x10000 * msec * (m_cursor_blink_speed / 1000.0);
|
||||
if (blink_increase == 0)
|
||||
blink_increase = 1;
|
||||
m_cursor_blink = ((m_cursor_blink + blink_increase) & 0xffff);
|
||||
}
|
||||
|
||||
// decrease open inhibit counter
|
||||
if (m_open_inhibited > msec)
|
||||
m_open_inhibited -= msec;
|
||||
else
|
||||
m_open_inhibited = 0;
|
||||
}
|
||||
|
||||
void GUIChatConsole::drawBackground()
|
||||
{
|
||||
video::IVideoDriver* driver = Environment->getVideoDriver();
|
||||
if (m_background != NULL)
|
||||
{
|
||||
core::rect<s32> sourcerect(0, -m_height, m_screensize.X, 0);
|
||||
driver->draw2DImage(
|
||||
m_background,
|
||||
v2s32(0, 0),
|
||||
sourcerect,
|
||||
&AbsoluteClippingRect,
|
||||
m_background_color,
|
||||
false);
|
||||
}
|
||||
else
|
||||
{
|
||||
driver->draw2DRectangle(
|
||||
m_background_color,
|
||||
core::rect<s32>(0, 0, m_screensize.X, m_height),
|
||||
&AbsoluteClippingRect);
|
||||
}
|
||||
}
|
||||
|
||||
void GUIChatConsole::drawText()
|
||||
{
|
||||
if (m_font == NULL)
|
||||
return;
|
||||
|
||||
ChatBuffer& buf = m_chat_backend->getConsoleBuffer();
|
||||
for (u32 row = 0; row < buf.getRows(); ++row)
|
||||
{
|
||||
const ChatFormattedLine& line = buf.getFormattedLine(row);
|
||||
if (line.fragments.empty())
|
||||
continue;
|
||||
|
||||
s32 line_height = m_fontsize.Y;
|
||||
s32 y = row * line_height + m_height - m_desired_height;
|
||||
if (y + line_height < 0)
|
||||
continue;
|
||||
|
||||
for (u32 i = 0; i < line.fragments.size(); ++i)
|
||||
{
|
||||
const ChatFormattedFragment& fragment = line.fragments[i];
|
||||
s32 x = (fragment.column + 1) * m_fontsize.X;
|
||||
core::rect<s32> destrect(
|
||||
x, y, x + m_fontsize.X * fragment.text.size(), y + m_fontsize.Y);
|
||||
m_font->draw(
|
||||
fragment.text.c_str(),
|
||||
destrect,
|
||||
video::SColor(255, 255, 255, 255),
|
||||
false,
|
||||
false,
|
||||
&AbsoluteClippingRect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GUIChatConsole::drawPrompt()
|
||||
{
|
||||
if (m_font == NULL)
|
||||
return;
|
||||
|
||||
u32 row = m_chat_backend->getConsoleBuffer().getRows();
|
||||
s32 line_height = m_fontsize.Y;
|
||||
s32 y = row * line_height + m_height - m_desired_height;
|
||||
|
||||
ChatPrompt& prompt = m_chat_backend->getPrompt();
|
||||
std::wstring prompt_text = prompt.getVisiblePortion();
|
||||
|
||||
// FIXME Draw string at once, not character by character
|
||||
// That will only work with the cursor once we have a monospace font
|
||||
for (u32 i = 0; i < prompt_text.size(); ++i)
|
||||
{
|
||||
wchar_t ws[2] = {prompt_text[i], 0};
|
||||
s32 x = (1 + i) * m_fontsize.X;
|
||||
core::rect<s32> destrect(
|
||||
x, y, x + m_fontsize.X, y + m_fontsize.Y);
|
||||
m_font->draw(
|
||||
ws,
|
||||
destrect,
|
||||
video::SColor(255, 255, 255, 255),
|
||||
false,
|
||||
false,
|
||||
&AbsoluteClippingRect);
|
||||
}
|
||||
|
||||
// Draw the cursor during on periods
|
||||
if ((m_cursor_blink & 0x8000) != 0)
|
||||
{
|
||||
s32 cursor_pos = prompt.getVisibleCursorPosition();
|
||||
if (cursor_pos >= 0)
|
||||
{
|
||||
video::IVideoDriver* driver = Environment->getVideoDriver();
|
||||
s32 x = (1 + cursor_pos) * m_fontsize.X;
|
||||
core::rect<s32> destrect(
|
||||
x,
|
||||
y + (1.0-m_cursor_height) * m_fontsize.Y,
|
||||
x + m_fontsize.X,
|
||||
y + m_fontsize.Y);
|
||||
video::SColor cursor_color(255,255,255,255);
|
||||
driver->draw2DRectangle(
|
||||
cursor_color,
|
||||
destrect,
|
||||
&AbsoluteClippingRect);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool GUIChatConsole::OnEvent(const SEvent& event)
|
||||
{
|
||||
if(event.EventType == EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown)
|
||||
{
|
||||
// Key input
|
||||
if(KeyPress(event.KeyInput) == getKeySetting("keymap_console"))
|
||||
{
|
||||
closeConsole();
|
||||
Environment->removeFocus(this);
|
||||
|
||||
// inhibit open so the_game doesn't reopen immediately
|
||||
m_open_inhibited = 50;
|
||||
return true;
|
||||
}
|
||||
else if(event.KeyInput.Key == KEY_ESCAPE)
|
||||
{
|
||||
closeConsoleAtOnce();
|
||||
Environment->removeFocus(this);
|
||||
// the_game will open the pause menu
|
||||
return true;
|
||||
}
|
||||
else if(event.KeyInput.Key == KEY_PRIOR)
|
||||
{
|
||||
m_chat_backend->scrollPageUp();
|
||||
return true;
|
||||
}
|
||||
else if(event.KeyInput.Key == KEY_NEXT)
|
||||
{
|
||||
m_chat_backend->scrollPageDown();
|
||||
return true;
|
||||
}
|
||||
else if(event.KeyInput.Key == KEY_RETURN)
|
||||
{
|
||||
std::wstring text = m_chat_backend->getPrompt().submit();
|
||||
m_client->typeChatMessage(text);
|
||||
return true;
|
||||
}
|
||||
else if(event.KeyInput.Key == KEY_UP)
|
||||
{
|
||||
// Up pressed
|
||||
// Move back in history
|
||||
m_chat_backend->getPrompt().historyPrev();
|
||||
return true;
|
||||
}
|
||||
else if(event.KeyInput.Key == KEY_DOWN)
|
||||
{
|
||||
// Down pressed
|
||||
// Move forward in history
|
||||
m_chat_backend->getPrompt().historyNext();
|
||||
return true;
|
||||
}
|
||||
else if(event.KeyInput.Key == KEY_LEFT)
|
||||
{
|
||||
// Left or Ctrl-Left pressed
|
||||
// move character / word to the left
|
||||
ChatPrompt::CursorOpScope scope =
|
||||
event.KeyInput.Control ?
|
||||
ChatPrompt::CURSOROP_SCOPE_WORD :
|
||||
ChatPrompt::CURSOROP_SCOPE_CHARACTER;
|
||||
m_chat_backend->getPrompt().cursorOperation(
|
||||
ChatPrompt::CURSOROP_MOVE,
|
||||
ChatPrompt::CURSOROP_DIR_LEFT,
|
||||
scope);
|
||||
return true;
|
||||
}
|
||||
else if(event.KeyInput.Key == KEY_RIGHT)
|
||||
{
|
||||
// Right or Ctrl-Right pressed
|
||||
// move character / word to the right
|
||||
ChatPrompt::CursorOpScope scope =
|
||||
event.KeyInput.Control ?
|
||||
ChatPrompt::CURSOROP_SCOPE_WORD :
|
||||
ChatPrompt::CURSOROP_SCOPE_CHARACTER;
|
||||
m_chat_backend->getPrompt().cursorOperation(
|
||||
ChatPrompt::CURSOROP_MOVE,
|
||||
ChatPrompt::CURSOROP_DIR_RIGHT,
|
||||
scope);
|
||||
return true;
|
||||
}
|
||||
else if(event.KeyInput.Key == KEY_HOME)
|
||||
{
|
||||
// Home pressed
|
||||
// move to beginning of line
|
||||
m_chat_backend->getPrompt().cursorOperation(
|
||||
ChatPrompt::CURSOROP_MOVE,
|
||||
ChatPrompt::CURSOROP_DIR_LEFT,
|
||||
ChatPrompt::CURSOROP_SCOPE_LINE);
|
||||
return true;
|
||||
}
|
||||
else if(event.KeyInput.Key == KEY_END)
|
||||
{
|
||||
// End pressed
|
||||
// move to end of line
|
||||
m_chat_backend->getPrompt().cursorOperation(
|
||||
ChatPrompt::CURSOROP_MOVE,
|
||||
ChatPrompt::CURSOROP_DIR_RIGHT,
|
||||
ChatPrompt::CURSOROP_SCOPE_LINE);
|
||||
return true;
|
||||
}
|
||||
else if(event.KeyInput.Key == KEY_BACK)
|
||||
{
|
||||
// Backspace or Ctrl-Backspace pressed
|
||||
// delete character / word to the left
|
||||
ChatPrompt::CursorOpScope scope =
|
||||
event.KeyInput.Control ?
|
||||
ChatPrompt::CURSOROP_SCOPE_WORD :
|
||||
ChatPrompt::CURSOROP_SCOPE_CHARACTER;
|
||||
m_chat_backend->getPrompt().cursorOperation(
|
||||
ChatPrompt::CURSOROP_DELETE,
|
||||
ChatPrompt::CURSOROP_DIR_LEFT,
|
||||
scope);
|
||||
return true;
|
||||
}
|
||||
else if(event.KeyInput.Key == KEY_DELETE)
|
||||
{
|
||||
// Delete or Ctrl-Delete pressed
|
||||
// delete character / word to the right
|
||||
ChatPrompt::CursorOpScope scope =
|
||||
event.KeyInput.Control ?
|
||||
ChatPrompt::CURSOROP_SCOPE_WORD :
|
||||
ChatPrompt::CURSOROP_SCOPE_CHARACTER;
|
||||
m_chat_backend->getPrompt().cursorOperation(
|
||||
ChatPrompt::CURSOROP_DELETE,
|
||||
ChatPrompt::CURSOROP_DIR_RIGHT,
|
||||
scope);
|
||||
return true;
|
||||
}
|
||||
else if(event.KeyInput.Key == KEY_KEY_U && event.KeyInput.Control)
|
||||
{
|
||||
// Ctrl-U pressed
|
||||
// kill line to left end
|
||||
m_chat_backend->getPrompt().cursorOperation(
|
||||
ChatPrompt::CURSOROP_DELETE,
|
||||
ChatPrompt::CURSOROP_DIR_LEFT,
|
||||
ChatPrompt::CURSOROP_SCOPE_LINE);
|
||||
return true;
|
||||
}
|
||||
else if(event.KeyInput.Key == KEY_KEY_K && event.KeyInput.Control)
|
||||
{
|
||||
// Ctrl-K pressed
|
||||
// kill line to right end
|
||||
m_chat_backend->getPrompt().cursorOperation(
|
||||
ChatPrompt::CURSOROP_DELETE,
|
||||
ChatPrompt::CURSOROP_DIR_RIGHT,
|
||||
ChatPrompt::CURSOROP_SCOPE_LINE);
|
||||
return true;
|
||||
}
|
||||
else if(event.KeyInput.Key == KEY_TAB)
|
||||
{
|
||||
// Tab or Shift-Tab pressed
|
||||
// Nick completion
|
||||
core::list<std::wstring> names = m_client->getConnectedPlayerNames();
|
||||
bool backwards = event.KeyInput.Shift;
|
||||
m_chat_backend->getPrompt().nickCompletion(names, backwards);
|
||||
return true;
|
||||
}
|
||||
else if(event.KeyInput.Char != 0 && !event.KeyInput.Control)
|
||||
{
|
||||
m_chat_backend->getPrompt().input(event.KeyInput.Char);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if(event.EventType == EET_MOUSE_INPUT_EVENT)
|
||||
{
|
||||
if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
|
||||
{
|
||||
s32 rows = myround(-3.0 * event.MouseInput.Wheel);
|
||||
m_chat_backend->scroll(rows);
|
||||
}
|
||||
}
|
||||
|
||||
return Parent ? Parent->OnEvent(event) : false;
|
||||
}
|
||||
|
125
src/guiChatConsole.h
Normal file
125
src/guiChatConsole.h
Normal file
@ -0,0 +1,125 @@
|
||||
/*
|
||||
Minetest-c55
|
||||
Copyright (C) 2011 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||
|
||||
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 2 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, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef GUICHATCONSOLE_HEADER
|
||||
#define GUICHATCONSOLE_HEADER
|
||||
|
||||
#include "common_irrlicht.h"
|
||||
#include "chat.h"
|
||||
|
||||
class Client;
|
||||
|
||||
class GUIChatConsole : public gui::IGUIElement
|
||||
{
|
||||
public:
|
||||
GUIChatConsole(gui::IGUIEnvironment* env,
|
||||
gui::IGUIElement* parent,
|
||||
s32 id,
|
||||
ChatBackend* backend,
|
||||
Client* client);
|
||||
virtual ~GUIChatConsole();
|
||||
|
||||
// Open the console (height = desired fraction of screen size)
|
||||
// This doesn't open immediately but initiates an animation.
|
||||
// You should call isOpenInhibited() before this.
|
||||
void openConsole(f32 height);
|
||||
// Check if the console should not be opened at the moment
|
||||
// This is to avoid reopening the console immediately after closing
|
||||
bool isOpenInhibited() const;
|
||||
// Close the console, equivalent to openConsole(0).
|
||||
// This doesn't close immediately but initiates an animation.
|
||||
void closeConsole();
|
||||
// Close the console immediately, without animation.
|
||||
void closeConsoleAtOnce();
|
||||
|
||||
// Return the desired height (fraction of screen size)
|
||||
// Zero if the console is closed or getting closed
|
||||
f32 getDesiredHeight() const;
|
||||
|
||||
// Change how the cursor looks
|
||||
void setCursor(
|
||||
bool visible,
|
||||
bool blinking = false,
|
||||
f32 blink_speed = 1.0,
|
||||
f32 relative_height = 1.0);
|
||||
|
||||
// Irrlicht draw method
|
||||
virtual void draw();
|
||||
|
||||
bool canTakeFocus(gui::IGUIElement* element) { return false; }
|
||||
|
||||
virtual bool OnEvent(const SEvent& event);
|
||||
|
||||
private:
|
||||
void reformatConsole();
|
||||
void recalculateConsolePosition();
|
||||
|
||||
// These methods are called by draw
|
||||
void animate(u32 msec);
|
||||
void drawBackground();
|
||||
void drawText();
|
||||
void drawPrompt();
|
||||
|
||||
private:
|
||||
// pointer to the chat backend
|
||||
ChatBackend* m_chat_backend;
|
||||
|
||||
// pointer to the client
|
||||
Client* m_client;
|
||||
|
||||
// current screen size
|
||||
v2u32 m_screensize;
|
||||
|
||||
// used to compute how much time passed since last animate()
|
||||
u32 m_animate_time_old;
|
||||
|
||||
// should the console be opened or closed?
|
||||
bool m_open;
|
||||
// current console height [pixels]
|
||||
s32 m_height;
|
||||
// desired height [pixels]
|
||||
f32 m_desired_height;
|
||||
// desired height [screen height fraction]
|
||||
f32 m_desired_height_fraction;
|
||||
// console open/close animation speed [screen height fraction / second]
|
||||
f32 m_height_speed;
|
||||
// if nonzero, opening the console is inhibited [milliseconds]
|
||||
u32 m_open_inhibited;
|
||||
|
||||
// cursor blink frame (16-bit value)
|
||||
// cursor is off during [0,32767] and on during [32768,65535]
|
||||
u32 m_cursor_blink;
|
||||
// cursor blink speed [on/off toggles / second]
|
||||
f32 m_cursor_blink_speed;
|
||||
// cursor height [line height]
|
||||
f32 m_cursor_height;
|
||||
|
||||
// background texture
|
||||
video::ITexture* m_background;
|
||||
// background color (including alpha)
|
||||
video::SColor m_background_color;
|
||||
|
||||
// font
|
||||
gui::IGUIFont* m_font;
|
||||
v2u32 m_fontsize;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
@ -261,7 +261,20 @@ void GUIKeyChangeMenu::regenerateGui(v2u32 screensize)
|
||||
this->cmd = Environment->addButton(rect, this, GUI_ID_KEY_CMD_BUTTON,
|
||||
wgettext(key_cmd.name()));
|
||||
}
|
||||
offset += v2s32(0, 25);
|
||||
{
|
||||
core::rect < s32 > rect(0, 0, 100, 20);
|
||||
rect += topleft + v2s32(offset.X, offset.Y);
|
||||
Environment->addStaticText(wgettext("Console"), rect, false, true, this, -1);
|
||||
//t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
|
||||
}
|
||||
|
||||
{
|
||||
core::rect < s32 > rect(0, 0, 100, 30);
|
||||
rect += topleft + v2s32(offset.X + 105, offset.Y - 5);
|
||||
this->console = Environment->addButton(rect, this, GUI_ID_KEY_CONSOLE_BUTTON,
|
||||
wgettext(key_console.name()));
|
||||
}
|
||||
|
||||
//next col
|
||||
offset = v2s32(250, 40);
|
||||
@ -371,6 +384,7 @@ bool GUIKeyChangeMenu::acceptInput()
|
||||
g_settings->set("keymap_inventory", key_inventory.sym());
|
||||
g_settings->set("keymap_chat", key_chat.sym());
|
||||
g_settings->set("keymap_cmd", key_cmd.sym());
|
||||
g_settings->set("keymap_console", key_console.sym());
|
||||
g_settings->set("keymap_rangeselect", key_range.sym());
|
||||
g_settings->set("keymap_freemove", key_fly.sym());
|
||||
g_settings->set("keymap_fastmove", key_fast.sym());
|
||||
@ -391,6 +405,7 @@ void GUIKeyChangeMenu::init_keys()
|
||||
key_inventory = getKeySetting("keymap_inventory");
|
||||
key_chat = getKeySetting("keymap_chat");
|
||||
key_cmd = getKeySetting("keymap_cmd");
|
||||
key_console = getKeySetting("keymap_console");
|
||||
key_range = getKeySetting("keymap_rangeselect");
|
||||
key_fly = getKeySetting("keymap_freemove");
|
||||
key_fast = getKeySetting("keymap_fastmove");
|
||||
@ -437,6 +452,9 @@ bool GUIKeyChangeMenu::resetMenu()
|
||||
case GUI_ID_KEY_CMD_BUTTON:
|
||||
this->cmd->setText(wgettext(key_cmd.name()));
|
||||
break;
|
||||
case GUI_ID_KEY_CONSOLE_BUTTON:
|
||||
this->console->setText(wgettext(key_console.name()));
|
||||
break;
|
||||
case GUI_ID_KEY_RANGE_BUTTON:
|
||||
this->range->setText(wgettext(key_range.name()));
|
||||
break;
|
||||
@ -516,6 +534,11 @@ bool GUIKeyChangeMenu::OnEvent(const SEvent& event)
|
||||
this->cmd->setText(wgettext(kp.name()));
|
||||
this->key_cmd = kp;
|
||||
}
|
||||
else if (activeKey == GUI_ID_KEY_CONSOLE_BUTTON)
|
||||
{
|
||||
this->console->setText(wgettext(kp.name()));
|
||||
this->key_console = kp;
|
||||
}
|
||||
else if (activeKey == GUI_ID_KEY_RANGE_BUTTON)
|
||||
{
|
||||
this->range->setText(wgettext(kp.name()));
|
||||
@ -630,6 +653,11 @@ bool GUIKeyChangeMenu::OnEvent(const SEvent& event)
|
||||
activeKey = event.GUIEvent.Caller->getID();
|
||||
this->cmd->setText(wgettext("press Key"));
|
||||
break;
|
||||
case GUI_ID_KEY_CONSOLE_BUTTON:
|
||||
resetMenu();
|
||||
activeKey = event.GUIEvent.Caller->getID();
|
||||
this->console->setText(wgettext("press Key"));
|
||||
break;
|
||||
case GUI_ID_KEY_SNEAK_BUTTON:
|
||||
resetMenu();
|
||||
activeKey = event.GUIEvent.Caller->getID();
|
||||
|
@ -44,6 +44,7 @@ enum
|
||||
GUI_ID_KEY_JUMP_BUTTON,
|
||||
GUI_ID_KEY_CHAT_BUTTON,
|
||||
GUI_ID_KEY_CMD_BUTTON,
|
||||
GUI_ID_KEY_CONSOLE_BUTTON,
|
||||
GUI_ID_KEY_SNEAK_BUTTON,
|
||||
GUI_ID_KEY_DROP_BUTTON,
|
||||
GUI_ID_KEY_INVENTORY_BUTTON,
|
||||
@ -91,6 +92,7 @@ private:
|
||||
gui::IGUIButton *dump;
|
||||
gui::IGUIButton *chat;
|
||||
gui::IGUIButton *cmd;
|
||||
gui::IGUIButton *console;
|
||||
|
||||
s32 activeKey;
|
||||
KeyPress key_forward;
|
||||
@ -107,6 +109,7 @@ private:
|
||||
KeyPress key_range;
|
||||
KeyPress key_chat;
|
||||
KeyPress key_cmd;
|
||||
KeyPress key_console;
|
||||
KeyPress key_dump;
|
||||
};
|
||||
|
||||
|
32
src/main.cpp
32
src/main.cpp
@ -403,6 +403,7 @@ Doing currently:
|
||||
#include "game.h"
|
||||
#include "keycode.h"
|
||||
#include "tile.h"
|
||||
#include "chat.h"
|
||||
#include "defaultsettings.h"
|
||||
#include "gettext.h"
|
||||
#include "settings.h"
|
||||
@ -940,20 +941,20 @@ void drawMenuBackground(video::IVideoDriver* driver)
|
||||
driver->getTexture(getTexturePath("menubg.png").c_str());
|
||||
if(bgtexture)
|
||||
{
|
||||
s32 texturesize = 128;
|
||||
s32 tiled_y = screensize.Height / texturesize + 1;
|
||||
s32 tiled_x = screensize.Width / texturesize + 1;
|
||||
s32 scaledsize = 128;
|
||||
|
||||
for(s32 y=0; y<tiled_y; y++)
|
||||
for(s32 x=0; x<tiled_x; x++)
|
||||
{
|
||||
core::rect<s32> rect(0,0,texturesize,texturesize);
|
||||
rect += v2s32(x*texturesize, y*texturesize);
|
||||
driver->draw2DImage(bgtexture, rect,
|
||||
core::rect<s32>(core::position2d<s32>(0,0),
|
||||
core::dimension2di(bgtexture->getSize())),
|
||||
NULL, NULL, true);
|
||||
}
|
||||
// The important difference between destsize and screensize is
|
||||
// that destsize is rounded to whole scaled pixels.
|
||||
// These formulas use component-wise multiplication and division of v2u32.
|
||||
v2u32 texturesize = bgtexture->getSize();
|
||||
v2u32 sourcesize = texturesize * screensize / scaledsize + v2u32(1,1);
|
||||
v2u32 destsize = scaledsize * sourcesize / texturesize;
|
||||
|
||||
// Default texture wrapping mode in Irrlicht is ETC_REPEAT.
|
||||
driver->draw2DImage(bgtexture,
|
||||
core::rect<s32>(0, 0, destsize.X, destsize.Y),
|
||||
core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y),
|
||||
NULL, NULL, true);
|
||||
}
|
||||
|
||||
video::ITexture *logotexture =
|
||||
@ -1479,6 +1480,8 @@ int main(int argc, char *argv[])
|
||||
GUI stuff
|
||||
*/
|
||||
|
||||
ChatBackend chat_backend;
|
||||
|
||||
/*
|
||||
If an error occurs, this is set to something and the
|
||||
menu-game loop is restarted. It is then displayed before
|
||||
@ -1663,7 +1666,8 @@ int main(int argc, char *argv[])
|
||||
address,
|
||||
port,
|
||||
error_message,
|
||||
configpath
|
||||
configpath,
|
||||
chat_backend
|
||||
);
|
||||
|
||||
} //try
|
||||
|
@ -694,6 +694,46 @@ private:
|
||||
u32 *m_result;
|
||||
};
|
||||
|
||||
// Tests if two strings are equal, optionally case insensitive
|
||||
inline bool str_equal(const std::wstring& s1, const std::wstring& s2,
|
||||
bool case_insensitive = false)
|
||||
{
|
||||
if(case_insensitive)
|
||||
{
|
||||
if(s1.size() != s2.size())
|
||||
return false;
|
||||
for(size_t i = 0; i < s1.size(); ++i)
|
||||
if(tolower(s1[i]) != tolower(s2[i]))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return s1 == s2;
|
||||
}
|
||||
}
|
||||
|
||||
// Tests if the second string is a prefix of the first, optionally case insensitive
|
||||
inline bool str_starts_with(const std::wstring& str, const std::wstring& prefix,
|
||||
bool case_insensitive = false)
|
||||
{
|
||||
if(str.size() < prefix.size())
|
||||
return false;
|
||||
if(case_insensitive)
|
||||
{
|
||||
for(size_t i = 0; i < prefix.size(); ++i)
|
||||
if(tolower(str[i]) != tolower(prefix[i]))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
for(size_t i = 0; i < prefix.size(); ++i)
|
||||
if(str[i] != prefix[i])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Calculates the borders of a "d-radius" cube
|
||||
inline void getFacePositions(core::list<v3s16> &list, u16 d)
|
||||
{
|
||||
@ -1565,6 +1605,15 @@ inline std::string wrap_rows(const std::string &from, u32 rowlen)
|
||||
#define MYMIN(a,b) ((a)<(b)?(a):(b))
|
||||
#define MYMAX(a,b) ((a)>(b)?(a):(b))
|
||||
|
||||
/*
|
||||
Returns nearest 32-bit integer for given floating point number.
|
||||
<cmath> and <math.h> in VC++ don't provide round().
|
||||
*/
|
||||
inline s32 myround(f32 f)
|
||||
{
|
||||
return floor(f + 0.5);
|
||||
}
|
||||
|
||||
/*
|
||||
Returns integer position of node in given floating point position
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user