mirror of
https://github.com/wesnoth/wesnoth
synced 2025-05-13 22:26:56 +00:00
Added support for selecting text, using keyboard or mouse, on textboxes.
This commit is contained in:
parent
c8f4b1728c
commit
8f43d2c5fd
85
src/font.cpp
85
src/font.cpp
@ -183,6 +183,22 @@ SDL_Surface* render_text(TTF_Font* font,const std::string& str,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
int text_size(TTF_Font* font,const std::string& str, int &w, int &h)
|
||||
{
|
||||
switch(charset())
|
||||
{
|
||||
case CHARSET_UTF8:
|
||||
return TTF_SizeUTF8(font, str.c_str(), &w, &h);
|
||||
case CHARSET_LATIN1:
|
||||
return TTF_SizeText(font, str.c_str(), &w, &h);
|
||||
default:
|
||||
std::cerr << "Unrecognized charset\n";
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
//function which will parse the markup tags at the front of a string
|
||||
std::string::const_iterator parse_markup(std::string::const_iterator i1, std::string::const_iterator i2,
|
||||
int* font_size, SDL_Color* colour)
|
||||
@ -234,11 +250,53 @@ std::string::const_iterator parse_markup(std::string::const_iterator i1, std::st
|
||||
|
||||
}
|
||||
|
||||
SDL_Rect draw_text_line(display* gui, const SDL_Rect& area, int size,
|
||||
const SDL_Color& colour, const std::string& text,
|
||||
int x, int y, SDL_Surface* bg, bool use_tooltips)
|
||||
/*
|
||||
SDL_Rect get_text_size(const std::string &str, int size)
|
||||
{
|
||||
TTF_Font* const font = get_font(size);
|
||||
|
||||
SDL_Rect tsize;
|
||||
int w, h;
|
||||
int res = text_size(font, str, w, h);
|
||||
if(res == -1) {
|
||||
std::cerr << "Could not get ttf size: '" << str << "'\n";
|
||||
SDL_Rect res;
|
||||
res.x = 0; res.y = 0; res.w = 0; res.h = 0;
|
||||
return res;
|
||||
}
|
||||
tsize.x = 0;
|
||||
tsize.y = 0;
|
||||
tsize.w = w;
|
||||
tsize.h = h;
|
||||
|
||||
return tsize;
|
||||
}
|
||||
*/
|
||||
|
||||
SDL_Surface* get_rendered_text(const std::string& str, int size,
|
||||
const SDL_Color& colour)
|
||||
{
|
||||
TTF_Font* const font = get_font(size);
|
||||
if(font == NULL) {
|
||||
std::cerr << "Could not get font\n";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_Surface *res = render_text(font,str,colour);
|
||||
if(res == NULL) {
|
||||
std::cerr << "Could not render ttf: '" << str << "'\n";
|
||||
return NULL;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
SDL_Rect draw_text_line(SDL_Surface *gui_surface, const SDL_Rect& area, int size,
|
||||
const SDL_Color& colour, const std::string& text,
|
||||
int x, int y, SDL_Surface* bg, bool use_tooltips)
|
||||
{
|
||||
|
||||
TTF_Font* const font = get_font(size);
|
||||
if(font == NULL) {
|
||||
std::cerr << "Could not get font\n";
|
||||
SDL_Rect res;
|
||||
@ -277,7 +335,7 @@ SDL_Rect draw_text_line(display* gui, const SDL_Rect& area, int size,
|
||||
std::fill(txt.end()-3,txt.end(),'.');
|
||||
}
|
||||
|
||||
return draw_text_line(gui,area,size,colour,txt,x,y,bg,false);
|
||||
return draw_text_line(gui_surface,area,size,colour,txt,x,y,bg,false);
|
||||
}
|
||||
|
||||
dest.w = area.x + area.w - dest.x;
|
||||
@ -287,11 +345,11 @@ SDL_Rect draw_text_line(display* gui, const SDL_Rect& area, int size,
|
||||
dest.h = area.y + area.h - dest.y;
|
||||
}
|
||||
|
||||
if(gui != NULL) {
|
||||
if(gui_surface != NULL) {
|
||||
SDL_Rect src = dest;
|
||||
src.x = 0;
|
||||
src.y = 0;
|
||||
sdl_safe_blit(surface,&src,gui->video().getSurface(),&dest);
|
||||
sdl_safe_blit(surface,&src,gui_surface,&dest);
|
||||
}
|
||||
|
||||
if(use_tooltips) {
|
||||
@ -301,6 +359,21 @@ SDL_Rect draw_text_line(display* gui, const SDL_Rect& area, int size,
|
||||
return dest;
|
||||
}
|
||||
|
||||
SDL_Rect draw_text_line(display* gui, const SDL_Rect& area, int size,
|
||||
const SDL_Color& colour, const std::string& text,
|
||||
int x, int y, SDL_Surface* bg, bool use_tooltips)
|
||||
{
|
||||
SDL_Surface * surface;
|
||||
|
||||
if(gui == NULL) {
|
||||
surface = NULL;
|
||||
} else {
|
||||
surface = gui->video().getSurface();
|
||||
}
|
||||
|
||||
return draw_text_line(surface, area, size, colour, text, x, y, bg, use_tooltips);
|
||||
}
|
||||
|
||||
SDL_Rect draw_text(display* gui, const SDL_Rect& area, int size,
|
||||
const SDL_Color& colour, const std::string& txt,
|
||||
int x, int y, SDL_Surface* bg, bool use_tooltips,
|
||||
|
@ -62,12 +62,15 @@ extern const char LARGE_TEXT, SMALL_TEXT, GOOD_TEXT, BAD_TEXT, NORMAL_TEXT, BLAC
|
||||
//
|
||||
//a bounding rectangle of the text is returned. If gui is NULL, then the
|
||||
//text will not be drawn, and a bounding rectangle only will be returned.
|
||||
|
||||
SDL_Rect draw_text(display* gui, const SDL_Rect& area, int size,
|
||||
const SDL_Color& colour, const std::string& text,
|
||||
int x, int y, SDL_Surface* bg=NULL,
|
||||
bool use_tooltips=false, MARKUP use_markup=USE_MARKUP);
|
||||
|
||||
|
||||
// Returns a SDL surface containing only the text rendered in a given colour.
|
||||
SDL_Surface* get_rendered_text(const std::string& str, int size,
|
||||
const SDL_Color& colour);
|
||||
|
||||
bool is_format_char(char c);
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "../font.hpp"
|
||||
#include "../show_dialog.hpp"
|
||||
#include "../video.hpp"
|
||||
#include "../util.hpp"
|
||||
#include "SDL.h"
|
||||
|
||||
#include <algorithm>
|
||||
@ -24,13 +25,15 @@ namespace gui {
|
||||
const int font_size = 16;
|
||||
|
||||
textbox::textbox(display& d, int width, const std::string& text)
|
||||
: widget(d), text_(text), firstOnScreen_(0),
|
||||
cursor_(text.size()), show_cursor_(true)
|
||||
: widget(d), text_(text), text_pos_(0),
|
||||
cursor_(text.size()), selstart_(-1), selend_(-1), grabmouse_(false),
|
||||
show_cursor_(true), text_image_(NULL)
|
||||
{
|
||||
static const SDL_Rect area = d.screen_area();
|
||||
const int height = font::draw_text(NULL,area,font_size,font::NORMAL_COLOUR,"ABCD",0,0).h;
|
||||
const SDL_Rect starting_rect = {0,0,width,height};
|
||||
set_location(starting_rect);
|
||||
update_text_cache(true);
|
||||
}
|
||||
|
||||
const std::string& textbox::text() const
|
||||
@ -43,14 +46,17 @@ void textbox::set_text(std::string text)
|
||||
text_ = text;
|
||||
cursor_ = text_.size();
|
||||
set_dirty(true);
|
||||
update_text_cache(true);
|
||||
}
|
||||
|
||||
void textbox::clear()
|
||||
{
|
||||
text_ = "";
|
||||
cursor_ = 0;
|
||||
firstOnScreen_ = 0;
|
||||
cursor_pos_ = 0;
|
||||
text_pos_ = 0;
|
||||
set_dirty(true);
|
||||
update_text_cache(true);
|
||||
}
|
||||
|
||||
void textbox::draw_cursor(int pos, display &disp) const
|
||||
@ -72,30 +78,36 @@ void textbox::draw()
|
||||
gui::draw_solid_tinted_rectangle(location().x,location().y,location().w,location().h,0,0,0,
|
||||
focus() ? 0.2 : 0.4, disp().video().getSurface());
|
||||
|
||||
int pos = 1;
|
||||
|
||||
const SDL_Rect clip = disp().screen_area();
|
||||
|
||||
//draw the text
|
||||
//the "firstOnScreen to cursor_" string should always fit in th rect. If this is not
|
||||
//the case, increase firstOnScreen until this is.
|
||||
while(true) {
|
||||
if (firstOnScreen_ == cursor_)
|
||||
break;
|
||||
|
||||
const std::string visible_string = text_.substr(firstOnScreen_, cursor_ - firstOnScreen_);
|
||||
const SDL_Rect area = font::draw_text(NULL,clip,font_size,font::NORMAL_COLOUR,visible_string,0,0,
|
||||
NULL,false,font::NO_MARKUP);
|
||||
pos = area.w;
|
||||
if(area.w <= location().w)
|
||||
break;
|
||||
++firstOnScreen_;
|
||||
SDL_Rect src;
|
||||
|
||||
// Fills the selected area
|
||||
if(is_selection()) {
|
||||
int x = minimum<int>(char_pos_[selstart_], char_pos_[selend_]) - text_pos_ + location().x;
|
||||
int w = abs(char_pos_[selstart_] - char_pos_[selend_]);
|
||||
|
||||
if(!((x > location().x + location().w) || ((x + w) < location().x))) {
|
||||
src.x = maximum<int>(x, location().x);
|
||||
src.y = location().y;
|
||||
src.w = src.x + w > location().x + location().w ? location().x + location().w - src.x : w;
|
||||
src.h = location().h;
|
||||
|
||||
Uint16 colour = Uint16(SDL_MapRGB(disp().video().getSurface()->format,
|
||||
font::YELLOW_COLOUR.r, font::YELLOW_COLOUR.g, font::YELLOW_COLOUR.b));
|
||||
SDL_FillRect(disp().video().getSurface(), &src, colour);
|
||||
}
|
||||
}
|
||||
|
||||
src.y = 0;
|
||||
src.w = text_size_.w;
|
||||
src.h = text_size_.h;
|
||||
src.x = text_pos_;
|
||||
SDL_Rect dest = disp().screen_area();
|
||||
dest.x = location().x;
|
||||
dest.y = location().y;
|
||||
SDL_BlitSurface(text_image_,&src,disp().video().getSurface(),&dest);
|
||||
|
||||
font::draw_text(&disp(),clip,font_size,font::NORMAL_COLOUR,text_.substr(firstOnScreen_),
|
||||
location().x + 1, location().y, NULL, false, font::NO_MARKUP);
|
||||
|
||||
draw_cursor(pos-1, disp());
|
||||
draw_cursor((cursor_pos_ == 0 ? 0 : cursor_pos_ - 1), disp());
|
||||
|
||||
set_dirty(false);
|
||||
update_rect(location());
|
||||
@ -112,65 +124,173 @@ void textbox::process()
|
||||
draw();
|
||||
}
|
||||
|
||||
void textbox::update_text_cache(bool changed)
|
||||
{
|
||||
if(changed) {
|
||||
char_pos_.clear();
|
||||
char_pos_.push_back(0);
|
||||
|
||||
// Re-calculate the position of each glyph. We approximate this by asking the
|
||||
// width of each substring, but this is a flawed assumption which won't work with
|
||||
// some more complex scripts (that is, RTL languages). This part of the work should
|
||||
// actually be done by the font-rendering system.
|
||||
for(int i = 0; i < text_.size(); ++i) {
|
||||
const std::string visible_string = text_.substr(0, i+1);
|
||||
const int w = font::line_width(visible_string, font_size);
|
||||
|
||||
char_pos_.push_back(w);
|
||||
}
|
||||
|
||||
text_size_.x = 0;
|
||||
text_size_.y = 0;
|
||||
text_size_.w = font::line_width(text_, font_size);
|
||||
text_size_.h = location().h;
|
||||
|
||||
text_image_.assign(font::get_rendered_text(text_, font_size, font::NORMAL_COLOUR));
|
||||
}
|
||||
|
||||
int cursor_x = char_pos_[cursor_];
|
||||
|
||||
if(cursor_x - text_pos_ > location().w) {
|
||||
text_pos_ = cursor_x - location().w;
|
||||
} else if(cursor_x - text_pos_ < 0) {
|
||||
text_pos_ = cursor_x;
|
||||
}
|
||||
cursor_pos_ = cursor_x - text_pos_;
|
||||
}
|
||||
|
||||
bool textbox::is_selection()
|
||||
{
|
||||
return (selstart_ != -1) && (selend_ != -1) && (selstart_ != selend_);
|
||||
}
|
||||
|
||||
void textbox::erase_selection()
|
||||
{
|
||||
if(!is_selection())
|
||||
return;
|
||||
|
||||
std::string::iterator itor = text_.begin() + minimum(selstart_, selend_);
|
||||
text_.erase(itor, itor + abs(selend_ - selstart_));
|
||||
cursor_ = minimum(selstart_, selend_);
|
||||
selstart_ = selend_ = -1;
|
||||
}
|
||||
|
||||
void textbox::handle_event(const SDL_Event& event)
|
||||
{
|
||||
bool changed = false;
|
||||
|
||||
if(location().x == 0)
|
||||
return;
|
||||
|
||||
int mousex, mousey;
|
||||
SDL_GetMouseState(&mousex,&mousey);
|
||||
const Uint8 mousebuttons = SDL_GetMouseState(&mousex,&mousey);
|
||||
if(!(mousebuttons & SDL_BUTTON(1))) {
|
||||
grabmouse_ = false;
|
||||
}
|
||||
|
||||
if(event.type != SDL_KEYDOWN || focus() != true)
|
||||
{
|
||||
if( (grabmouse_ && (event.type == SDL_MOUSEMOTION)) || (
|
||||
event.type == SDL_MOUSEBUTTONDOWN && (mousebuttons & SDL_BUTTON(1)) && !
|
||||
(mousex < location().x || mousex > location().x + location().w ||
|
||||
mousey < location().y || mousey > location().y + location().h))) {
|
||||
|
||||
const int x = mousex - location().x + text_pos_;
|
||||
const int y = mousey - location().y;
|
||||
int pos = 0;
|
||||
int distance = x;
|
||||
|
||||
for(int i = 1; i <= char_pos_.size(); ++i) {
|
||||
// Check individually each distance (if, one day, we support
|
||||
// RTL languages, char_pos[c] may not be monotonous.)
|
||||
if(abs(x - char_pos_[i]) < distance) {
|
||||
pos = i;
|
||||
distance = abs(x - char_pos_[i]);
|
||||
}
|
||||
}
|
||||
|
||||
cursor_ = pos;
|
||||
|
||||
if(grabmouse_)
|
||||
selend_ = cursor_;
|
||||
|
||||
update_text_cache(false);
|
||||
|
||||
if(!grabmouse_ && mousebuttons & SDL_BUTTON(1)) {
|
||||
grabmouse_ = true;
|
||||
selstart_ = selend_ = cursor_;
|
||||
} else if (! (mousebuttons & SDL_BUTTON(1))) {
|
||||
grabmouse_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(event.type != SDL_KEYDOWN || focus() != true) {
|
||||
draw();
|
||||
return;
|
||||
}
|
||||
|
||||
const SDL_keysym& key = reinterpret_cast<const SDL_KeyboardEvent&>(event).keysym;
|
||||
const SDLMod modifiers = SDL_GetModState();
|
||||
|
||||
const int c = key.sym;
|
||||
|
||||
if(c == SDLK_LEFT && cursor_ > 0) {
|
||||
|
||||
int old_cursor = cursor_;
|
||||
|
||||
if(c == SDLK_LEFT && cursor_ > 0)
|
||||
--cursor_;
|
||||
if(cursor_ < firstOnScreen_)
|
||||
--firstOnScreen_;
|
||||
}
|
||||
|
||||
if(c == SDLK_RIGHT && cursor_ < text_.size()) {
|
||||
if(c == SDLK_RIGHT && cursor_ < text_.size())
|
||||
++cursor_;
|
||||
}
|
||||
|
||||
if(c == SDLK_END)
|
||||
cursor_ = text_.size();
|
||||
|
||||
if(c == SDLK_HOME)
|
||||
cursor_ = 0;
|
||||
|
||||
if((old_cursor != cursor_) && (modifiers & KMOD_SHIFT)) {
|
||||
if(selstart_ == -1)
|
||||
selstart_ = old_cursor;
|
||||
selend_ = cursor_;
|
||||
}
|
||||
|
||||
if(c == SDLK_BACKSPACE && cursor_ > 0) {
|
||||
--cursor_;
|
||||
text_.erase(text_.begin()+cursor_);
|
||||
if(cursor_ < firstOnScreen_)
|
||||
--firstOnScreen_;
|
||||
}
|
||||
|
||||
if(c == SDLK_DELETE && !text_.empty()) {
|
||||
if(cursor_ == text_.size()) {
|
||||
text_.resize(text_.size()-1);
|
||||
--cursor_;
|
||||
changed = true;
|
||||
if(is_selection()) {
|
||||
erase_selection();
|
||||
} else {
|
||||
--cursor_;
|
||||
text_.erase(text_.begin()+cursor_);
|
||||
}
|
||||
}
|
||||
|
||||
if(c == SDLK_END) {
|
||||
cursor_ = text_.size();
|
||||
if(c == SDLK_DELETE && !text_.empty()) {
|
||||
changed = true;
|
||||
if(is_selection()) {
|
||||
erase_selection();
|
||||
} else {
|
||||
if(cursor_ == text_.size()) {
|
||||
text_.resize(text_.size()-1);
|
||||
--cursor_;
|
||||
} else {
|
||||
text_.erase(text_.begin()+cursor_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(c == SDLK_HOME) {
|
||||
cursor_ = firstOnScreen_ = 0;
|
||||
}
|
||||
|
||||
|
||||
const char character = static_cast<char>(key.unicode);
|
||||
|
||||
if(isgraph(character) || character == ' ') {
|
||||
changed = true;
|
||||
if(is_selection())
|
||||
erase_selection();
|
||||
|
||||
text_.insert(text_.begin()+cursor_,character);
|
||||
++cursor_;
|
||||
++cursor_;
|
||||
}
|
||||
|
||||
if(is_selection() && (selend_ != cursor_))
|
||||
selstart_ = selend_ = -1;
|
||||
|
||||
update_text_cache(true);
|
||||
set_dirty(true);
|
||||
draw();
|
||||
}
|
||||
|
@ -41,14 +41,28 @@ protected:
|
||||
|
||||
private:
|
||||
std::string text_;
|
||||
mutable unsigned int firstOnScreen_;
|
||||
unsigned int cursor_;
|
||||
|
||||
// mutable unsigned int firstOnScreen_;
|
||||
int cursor_;
|
||||
int selstart_;
|
||||
int selend_;
|
||||
bool grabmouse_;
|
||||
|
||||
int text_pos_;
|
||||
int cursor_pos_;
|
||||
std::vector<int> char_pos_;
|
||||
|
||||
bool show_cursor_;
|
||||
shared_sdl_surface text_image_;
|
||||
SDL_Rect text_size_;
|
||||
|
||||
void handle_event(const SDL_Event& event);
|
||||
|
||||
void draw();
|
||||
void draw_cursor(int pos, display &disp) const;
|
||||
void update_text_cache(bool reset = false);
|
||||
bool is_selection();
|
||||
void erase_selection();
|
||||
};
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user