Added support for selecting text, using keyboard or mouse, on textboxes.

This commit is contained in:
Philippe Plantier 2004-04-23 21:14:20 +00:00
parent c8f4b1728c
commit 8f43d2c5fd
4 changed files with 271 additions and 61 deletions

View File

@ -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,

View File

@ -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);

View File

@ -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();
}

View File

@ -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();
};
}