diff options
author | Mathias Gumz <akira at fluxbox dot org> | 2012-10-02 12:24:47 (GMT) |
---|---|---|
committer | Mathias Gumz <akira at fluxbox dot org> | 2012-10-03 08:27:16 (GMT) |
commit | 7b6ab828c7e5453a2720462156d165707935c9ef (patch) | |
tree | 3b641791b20b1994935e149fd7461decb11bef8c /src/FbTk | |
parent | 032a23d1e790c5224194562a837cc80fc157ce9b (diff) | |
download | fluxbox-7b6ab828c7e5453a2720462156d165707935c9ef.zip fluxbox-7b6ab828c7e5453a2720462156d165707935c9ef.tar.bz2 |
Improved vertical alignment of text in FbTk::TextButton
The old formula for vertical align text inside FbTk::TextButton ('height/2 +
font_ascent/2 - 1') produced not always good looking results, escpecially
when different fonts are involved (eg, ClockTool and WorkspaceName have
different fonts and font-sizes).
'(height - font_ascent) / 2 - 1' produces better results.
Additional changes:
* added ASCII-Art to document the involved entities when calculating the
baseline
* rewritten tests/testFont.cc to accept multiples texts and multiple
fonts
* removed some internal parts of FbTk::Font from the public interface
Diffstat (limited to 'src/FbTk')
-rw-r--r-- | src/FbTk/Font.cc | 75 | ||||
-rw-r--r-- | src/FbTk/Font.hh | 17 | ||||
-rw-r--r-- | src/FbTk/TextButton.cc | 78 | ||||
-rw-r--r-- | src/FbTk/TextButton.hh | 4 |
4 files changed, 109 insertions, 65 deletions
diff --git a/src/FbTk/Font.cc b/src/FbTk/Font.cc index 37621a8..544ddc2 100644 --- a/src/FbTk/Font.cc +++ b/src/FbTk/Font.cc | |||
@@ -66,6 +66,7 @@ | |||
66 | #include <map> | 66 | #include <map> |
67 | #include <typeinfo> | 67 | #include <typeinfo> |
68 | #include <langinfo.h> | 68 | #include <langinfo.h> |
69 | #include <cstdio> | ||
69 | 70 | ||
70 | #include <errno.h> | 71 | #include <errno.h> |
71 | 72 | ||
@@ -83,12 +84,10 @@ namespace { | |||
83 | // use to map <font1>|<font2>|<font3> => <fontthatworks> | 84 | // use to map <font1>|<font2>|<font3> => <fontthatworks> |
84 | typedef map<string, string> StringMap; | 85 | typedef map<string, string> StringMap; |
85 | typedef StringMap::iterator StringMapIt; | 86 | typedef StringMap::iterator StringMapIt; |
86 | StringMap lookup_map; | ||
87 | 87 | ||
88 | // stores <fontthatworks and the fontimp | 88 | // stores <fontthatworks and the fontimp |
89 | typedef map<string, FbTk::FontImp* > FontCache; | 89 | typedef map<string, FbTk::FontImp* > FontCache; |
90 | typedef FontCache::iterator FontCacheIt; | 90 | typedef FontCache::iterator FontCacheIt; |
91 | FontCache font_cache; | ||
92 | 91 | ||
93 | 92 | ||
94 | void resetEffects(FbTk::Font& font) { | 93 | void resetEffects(FbTk::Font& font) { |
@@ -100,24 +99,30 @@ void resetEffects(FbTk::Font& font) { | |||
100 | font.setShadowOffX(2); | 99 | font.setShadowOffX(2); |
101 | } | 100 | } |
102 | 101 | ||
102 | |||
103 | StringMap s_lookup_map; | ||
104 | FontCache s_font_cache; | ||
105 | bool s_multibyte = false; // if the fontimp should be a multibyte font | ||
106 | bool s_utf8mode = false; // should the font use utf8 font imp | ||
107 | |||
108 | |||
103 | } // end nameless namespace | 109 | } // end nameless namespace |
104 | 110 | ||
105 | 111 | ||
106 | 112 | ||
107 | namespace FbTk { | 113 | namespace FbTk { |
108 | 114 | ||
109 | bool Font::s_multibyte = false; | 115 | const char Font::DEFAULT_FONT[] = "__DEFAULT__"; |
110 | bool Font::s_utf8mode = false; | ||
111 | 116 | ||
112 | 117 | ||
113 | void Font::shutdown() { | 118 | void Font::shutdown() { |
114 | 119 | ||
115 | FontCacheIt fit; | 120 | FontCacheIt fit; |
116 | for (fit = font_cache.begin(); fit != font_cache.end(); fit++) { | 121 | for (fit = s_font_cache.begin(); fit != s_font_cache.end(); ++fit) { |
117 | FontImp* font = fit->second; | 122 | FontImp* font = fit->second; |
118 | if (font) { | 123 | if (font) { |
119 | FontCacheIt it; | 124 | FontCacheIt it; |
120 | for (it = fit; it != font_cache.end(); ++it) | 125 | for (it = fit; it != s_font_cache.end(); ++it) |
121 | if (it->second == font) | 126 | if (it->second == font) |
122 | it->second = 0; | 127 | it->second = 0; |
123 | delete font; | 128 | delete font; |
@@ -125,6 +130,15 @@ void Font::shutdown() { | |||
125 | } | 130 | } |
126 | } | 131 | } |
127 | 132 | ||
133 | bool Font::multibyte() { | ||
134 | return s_multibyte; | ||
135 | } | ||
136 | |||
137 | bool Font::utf8() { | ||
138 | return s_utf8mode; | ||
139 | } | ||
140 | |||
141 | |||
128 | Font::Font(const char *name): | 142 | Font::Font(const char *name): |
129 | m_fontimp(0), | 143 | m_fontimp(0), |
130 | m_shadow(false), m_shadow_color("black", DefaultScreen(App::instance()->display())), | 144 | m_shadow(false), m_shadow_color("black", DefaultScreen(App::instance()->display())), |
@@ -135,13 +149,15 @@ Font::Font(const char *name): | |||
135 | if (MB_CUR_MAX > 1) // more than one byte, then we're multibyte | 149 | if (MB_CUR_MAX > 1) // more than one byte, then we're multibyte |
136 | s_multibyte = true; | 150 | s_multibyte = true; |
137 | 151 | ||
138 | // check for utf-8 mode | 152 | |
139 | #if defined(CODESET) && !defined(_WIN32) | ||
140 | char *locale_codeset = nl_langinfo(CODESET); | ||
141 | #else // openbsd doesnt have this (yet?) | ||
142 | char *locale_codeset = 0; | 153 | char *locale_codeset = 0; |
143 | #endif // defined(CODESET) && !defined(_WIN32) | ||
144 | 154 | ||
155 | // openbsd doesnt have this (yet?) | ||
156 | #if defined(CODESET) && !defined(_WIN32) | ||
157 | locale_codeset = nl_langinfo(CODESET); | ||
158 | #endif | ||
159 | |||
160 | // check for utf-8 mode | ||
145 | if (locale_codeset && strcmp("UTF-8", locale_codeset) == 0) { | 161 | if (locale_codeset && strcmp("UTF-8", locale_codeset) == 0) { |
146 | s_utf8mode = true; | 162 | s_utf8mode = true; |
147 | } else if (locale_codeset != 0) { | 163 | } else if (locale_codeset != 0) { |
@@ -159,15 +175,15 @@ Font::~Font() { | |||
159 | 175 | ||
160 | bool Font::load(const string &name) { | 176 | bool Font::load(const string &name) { |
161 | 177 | ||
162 | if (name.size() == 0) | 178 | if (name.empty()) |
163 | return false; | 179 | return false; |
164 | 180 | ||
165 | StringMapIt lookup_entry; | 181 | StringMapIt lookup_entry; |
166 | FontCacheIt cache_entry; | 182 | FontCacheIt cache_entry; |
167 | 183 | ||
168 | // check if one of <font1>|<font2>|<font3> is already there | 184 | // check if one of <font1>|<font2>|<font3> is already there |
169 | if ((lookup_entry = lookup_map.find(name)) != lookup_map.end() && | 185 | if ((lookup_entry = s_lookup_map.find(name)) != s_lookup_map.end() && |
170 | (cache_entry = font_cache.find(lookup_entry->second)) != font_cache.end()) { | 186 | (cache_entry = s_font_cache.find(lookup_entry->second)) != s_font_cache.end()) { |
171 | m_fontstr = cache_entry->first; | 187 | m_fontstr = cache_entry->first; |
172 | m_fontimp = cache_entry->second; | 188 | m_fontimp = cache_entry->second; |
173 | resetEffects(*this); | 189 | resetEffects(*this); |
@@ -185,10 +201,10 @@ bool Font::load(const string &name) { | |||
185 | FbTk::StringUtil::removeTrailingWhitespace(*name_it); | 201 | FbTk::StringUtil::removeTrailingWhitespace(*name_it); |
186 | FbTk::StringUtil::removeFirstWhitespace(*name_it); | 202 | FbTk::StringUtil::removeFirstWhitespace(*name_it); |
187 | 203 | ||
188 | if ((cache_entry = font_cache.find(*name_it)) != font_cache.end()) { | 204 | if ((cache_entry = s_font_cache.find(*name_it)) != s_font_cache.end()) { |
189 | m_fontstr = cache_entry->first; | 205 | m_fontstr = cache_entry->first; |
190 | m_fontimp = cache_entry->second; | 206 | m_fontimp = cache_entry->second; |
191 | lookup_map[name] = m_fontstr; | 207 | s_lookup_map[name] = m_fontstr; |
192 | resetEffects(*this); | 208 | resetEffects(*this); |
193 | return true; | 209 | return true; |
194 | } | 210 | } |
@@ -210,32 +226,33 @@ bool Font::load(const string &name) { | |||
210 | #ifdef USE_XFT | 226 | #ifdef USE_XFT |
211 | if ((*name_it)[0] != '-') { | 227 | if ((*name_it)[0] != '-') { |
212 | 228 | ||
213 | if (*name_it == "__DEFAULT__") | 229 | if (*name_it == Font::DEFAULT_FONT) |
214 | realname = "monospace"; | 230 | realname = "monospace"; |
215 | 231 | ||
216 | tmp_font = new XftFontImp(0, s_utf8mode); | 232 | tmp_font = new XftFontImp(0, utf8()); |
217 | } | 233 | } |
218 | #endif // USE_XFT | 234 | #endif // USE_XFT |
219 | 235 | ||
220 | if (!tmp_font) { | 236 | if (!tmp_font) { |
221 | if (*name_it == "__DEFAULT__") | 237 | if (*name_it == Font::DEFAULT_FONT) |
222 | realname = "fixed"; | 238 | realname = "fixed"; |
223 | 239 | ||
224 | #ifdef USE_XMB | 240 | #ifdef USE_XMB |
225 | 241 | ||
226 | if (s_multibyte || s_utf8mode) { | 242 | if (multibyte() || utf8()) { |
227 | tmp_font = new XmbFontImp(0, s_utf8mode); | 243 | tmp_font = new XmbFontImp(0, utf8()); |
228 | } else // basic font implementation | 244 | } |
229 | #endif // USE_XMB | 245 | #endif // USE_XMB |
230 | { | 246 | } |
231 | tmp_font = new XFontImp(); | 247 | |
232 | } | 248 | if (!tmp_font) { |
249 | tmp_font = new XFontImp(); | ||
233 | } | 250 | } |
234 | 251 | ||
235 | if (tmp_font && tmp_font->load(realname.c_str())) { | 252 | if (tmp_font && tmp_font->load(realname.c_str())) { |
236 | lookup_map[name] = (*name_it); | 253 | s_lookup_map[name] = (*name_it); |
237 | m_fontimp = tmp_font; | 254 | m_fontimp = tmp_font; |
238 | font_cache[(*name_it)] = tmp_font; | 255 | s_font_cache[(*name_it)] = tmp_font; |
239 | m_fontstr = name; | 256 | m_fontstr = name; |
240 | resetEffects(*this); | 257 | resetEffects(*this); |
241 | return true; | 258 | return true; |
@@ -251,6 +268,10 @@ unsigned int Font::textWidth(const char* text, unsigned int size) const { | |||
251 | return m_fontimp->textWidth(text, size); | 268 | return m_fontimp->textWidth(text, size); |
252 | } | 269 | } |
253 | 270 | ||
271 | unsigned int Font::textWidth(const BiDiString &text) const { | ||
272 | return textWidth(text.visual().c_str(), text.visual().size()); | ||
273 | } | ||
274 | |||
254 | unsigned int Font::height() const { | 275 | unsigned int Font::height() const { |
255 | return m_fontimp->height(); | 276 | return m_fontimp->height(); |
256 | } | 277 | } |
diff --git a/src/FbTk/Font.hh b/src/FbTk/Font.hh index fb399d9..6ea2571 100644 --- a/src/FbTk/Font.hh +++ b/src/FbTk/Font.hh | |||
@@ -43,17 +43,19 @@ class FbDrawable; | |||
43 | class Font { | 43 | class Font { |
44 | public: | 44 | public: |
45 | 45 | ||
46 | static const char DEFAULT_FONT[]; | ||
47 | |||
48 | |||
46 | /// called at FbTk::App destruction time, cleans up cache | 49 | /// called at FbTk::App destruction time, cleans up cache |
47 | static void shutdown(); | 50 | static void shutdown(); |
48 | 51 | ||
49 | /// @return true if multibyte is enabled, else false | 52 | /// @return true if multibyte is enabled, else false |
50 | static bool multibyte() { return s_multibyte; } | 53 | static bool multibyte(); |
51 | /// @return true if utf-8 mode is enabled, else false | 54 | /// @return true if utf-8 mode is enabled, else false |
52 | static bool utf8() { return s_utf8mode; } | 55 | static bool utf8(); |
53 | |||
54 | 56 | ||
55 | 57 | ||
56 | explicit Font(const char *name = "__DEFAULT__"); | 58 | explicit Font(const char* name = DEFAULT_FONT); |
57 | virtual ~Font(); | 59 | virtual ~Font(); |
58 | /** | 60 | /** |
59 | Load a font | 61 | Load a font |
@@ -76,9 +78,7 @@ public: | |||
76 | @return size of text in pixels | 78 | @return size of text in pixels |
77 | */ | 79 | */ |
78 | unsigned int textWidth(const char* text, unsigned int size) const; | 80 | unsigned int textWidth(const char* text, unsigned int size) const; |
79 | unsigned int textWidth(const BiDiString &text) const { | 81 | unsigned int textWidth(const BiDiString &text) const; |
80 | return textWidth(text.visual().c_str(), text.visual().size()); | ||
81 | } | ||
82 | 82 | ||
83 | unsigned int height() const; | 83 | unsigned int height() const; |
84 | int ascent() const; | 84 | int ascent() const; |
@@ -119,9 +119,6 @@ private: | |||
119 | FbTk::FontImp* m_fontimp; ///< font implementation | 119 | FbTk::FontImp* m_fontimp; ///< font implementation |
120 | std::string m_fontstr; ///< font name | 120 | std::string m_fontstr; ///< font name |
121 | 121 | ||
122 | static bool s_multibyte; ///< if the fontimp should be a multibyte font | ||
123 | static bool s_utf8mode; ///< should the font use utf8 font imp | ||
124 | |||
125 | int m_angle; ///< rotation angle | 122 | int m_angle; ///< rotation angle |
126 | bool m_shadow; ///< shadow text | 123 | bool m_shadow; ///< shadow text |
127 | Color m_shadow_color; ///< shadow color | 124 | Color m_shadow_color; ///< shadow color |
diff --git a/src/FbTk/TextButton.cc b/src/FbTk/TextButton.cc index c31e3d6..33a2b44 100644 --- a/src/FbTk/TextButton.cc +++ b/src/FbTk/TextButton.cc | |||
@@ -23,6 +23,7 @@ | |||
23 | #include "TextUtils.hh" | 23 | #include "TextUtils.hh" |
24 | #include "Font.hh" | 24 | #include "Font.hh" |
25 | #include "GContext.hh" | 25 | #include "GContext.hh" |
26 | #include <cstdio> | ||
26 | 27 | ||
27 | namespace FbTk { | 28 | namespace FbTk { |
28 | 29 | ||
@@ -62,16 +63,18 @@ void TextButton::setJustify(FbTk::Justify just) { | |||
62 | } | 63 | } |
63 | 64 | ||
64 | bool TextButton::setOrientation(FbTk::Orientation orient) { | 65 | bool TextButton::setOrientation(FbTk::Orientation orient) { |
66 | |||
65 | if (orient == m_orientation | 67 | if (orient == m_orientation |
66 | || !m_font->validOrientation(orient)) | 68 | || !m_font->validOrientation(orient)) |
67 | return false; | 69 | return false; |
70 | |||
68 | invalidateBackground(); | 71 | invalidateBackground(); |
69 | 72 | ||
70 | if (((m_orientation == FbTk::ROT0 || m_orientation == FbTk::ROT180) && | 73 | if (((m_orientation == FbTk::ROT0 || m_orientation == FbTk::ROT180) && |
71 | (orient == FbTk::ROT90 || orient == FbTk::ROT270)) || | 74 | (orient == FbTk::ROT90 || orient == FbTk::ROT270)) || |
72 | ((m_orientation == FbTk::ROT90 || m_orientation == FbTk::ROT270) && | 75 | ((m_orientation == FbTk::ROT90 || m_orientation == FbTk::ROT270) && |
73 | (orient == FbTk::ROT0 || orient == FbTk::ROT180))) { | 76 | (orient == FbTk::ROT0 || orient == FbTk::ROT180))) { |
74 | // flip width and height | 77 | |
75 | m_orientation = orient; | 78 | m_orientation = orient; |
76 | resize(height(), width()); | 79 | resize(height(), width()); |
77 | } else { | 80 | } else { |
@@ -112,8 +115,7 @@ void TextButton::setTextPadding(unsigned int padding) { | |||
112 | 115 | ||
113 | /// clear window and redraw text | 116 | /// clear window and redraw text |
114 | void TextButton::clear() { | 117 | void TextButton::clear() { |
115 | TextButton::clearArea(0, 0, | 118 | TextButton::clearArea(0, 0, width(), height()); |
116 | width(), height()); | ||
117 | } | 119 | } |
118 | 120 | ||
119 | void TextButton::clearArea(int x, int y, | 121 | void TextButton::clearArea(int x, int y, |
@@ -135,34 +137,58 @@ void TextButton::renderForeground(FbWindow &win, FbDrawable &drawable) { | |||
135 | } | 137 | } |
136 | 138 | ||
137 | void TextButton::drawText(int x_offset, int y_offset, FbDrawable *drawable) { | 139 | void TextButton::drawText(int x_offset, int y_offset, FbDrawable *drawable) { |
140 | |||
141 | if (drawable == 0) | ||
142 | drawable = this; | ||
143 | |||
138 | const FbString& visual = text().visual(); | 144 | const FbString& visual = text().visual(); |
139 | unsigned int textlen = visual.size(); | 145 | unsigned int textlen = visual.size(); |
140 | unsigned int textw = width(); | 146 | unsigned int button_width = width(); |
141 | unsigned int texth = height(); | 147 | unsigned int button_height = height(); |
142 | translateSize(m_orientation, textw, texth); | 148 | |
149 | translateSize(m_orientation, button_width, button_height); | ||
143 | 150 | ||
144 | int align_x = FbTk::doAlignment(textw - x_offset - m_left_padding - m_right_padding, | 151 | // horizontal alignment, cut off text if needed |
152 | int align_x = FbTk::doAlignment(button_width - x_offset - m_left_padding - m_right_padding, | ||
145 | bevel(), justify(), font(), | 153 | bevel(), justify(), font(), |
146 | visual.data(), visual.size(), | 154 | visual.data(), visual.size(), |
147 | textlen); // return new text len | 155 | textlen); // return new text len |
148 | 156 | ||
149 | // center text by default | 157 | // |
150 | int center_pos = texth/2 + font().ascent()/2 - 1; | 158 | // we center the text vertically. to do this we have to nudge the |
151 | 159 | // baseline a little bit down so the "middle" of the glyph is placed | |
152 | int textx = align_x + x_offset + m_left_padding; | 160 | // on the middle of the button. we "assume", that ascent/2 is roughly |
153 | int texty = center_pos + y_offset; | 161 | // the middle of the glyph. example: |
154 | 162 | // | |
155 | if (drawable == 0) | 163 | // +== ascent <--------->== +===================== |
156 | drawable = this; | 164 | // | | | | |
165 | // | | ggggg | | ascent <---------> | ||
166 | // | | g gg | | | | | ||
167 | // | baseline < glyph | | | ggggg | | ||
168 | // | | g | -- middle of button -- | | g gg | | ||
169 | // | descent < ggggg | | baseline < glyph | | ||
170 | // | height |_________| | | g | | ||
171 | // | | descent < ggggg | | ||
172 | // | | height |_________| | ||
173 | // | | | ||
174 | // +======================= +===================== | ||
175 | // | ||
176 | // ascent = 4 | ||
177 | // button_height = 11 | ||
178 | // baseline = (11 + 4) / 2 - 1 = 6 | ||
179 | // | ||
180 | |||
181 | int baseline_x = align_x + x_offset + m_left_padding; | ||
182 | int baseline_y = ((button_height + font().ascent()) / 2) - 1 + y_offset; | ||
183 | |||
184 | // TODO: remove debug output fprintf(stderr, "%d | %d %d %d\n", height(), font().height(), font().ascent(), font().descent()); | ||
157 | 185 | ||
158 | // give it ROT0 style coords | 186 | // give it ROT0 style coords |
159 | translateCoords(m_orientation, textx, texty, textw, texth); | 187 | translateCoords(m_orientation, baseline_x, baseline_y, button_width, button_height); |
160 | 188 | ||
161 | font().drawText(*drawable, | 189 | font().drawText(*drawable, screenNumber(), gc(), |
162 | screenNumber(), | 190 | visual.c_str(), textlen, |
163 | gc(), // graphic context | 191 | baseline_x, baseline_y, m_orientation); |
164 | visual.c_str(), textlen, // string and string size | ||
165 | textx, texty, m_orientation); // position | ||
166 | } | 192 | } |
167 | 193 | ||
168 | 194 | ||
@@ -170,15 +196,15 @@ bool TextButton::textExceeds(int x_offset) { | |||
170 | 196 | ||
171 | const FbString& visual = text().visual(); | 197 | const FbString& visual = text().visual(); |
172 | unsigned int textlen = visual.size(); | 198 | unsigned int textlen = visual.size(); |
173 | unsigned int textw = width(); | 199 | unsigned int button_width = width(); |
174 | unsigned int texth = height(); | 200 | unsigned int button_height = height(); |
175 | translateSize(m_orientation, textw, texth); | 201 | translateSize(m_orientation, button_width, button_height); |
176 | 202 | ||
177 | FbTk::doAlignment(textw - x_offset - m_left_padding - m_right_padding, | 203 | FbTk::doAlignment(button_width - x_offset - m_left_padding - m_right_padding, |
178 | bevel(), justify(), font(), visual.data(), visual.size(), | 204 | bevel(), justify(), font(), visual.data(), visual.size(), |
179 | textlen); // return new text len | 205 | textlen); // return new text len |
180 | 206 | ||
181 | return visual.size()>textlen; | 207 | return visual.size() > textlen; |
182 | } | 208 | } |
183 | 209 | ||
184 | void TextButton::exposeEvent(XExposeEvent &event) { | 210 | void TextButton::exposeEvent(XExposeEvent &event) { |
diff --git a/src/FbTk/TextButton.hh b/src/FbTk/TextButton.hh index a1c8b0e..eb0beca 100644 --- a/src/FbTk/TextButton.hh +++ b/src/FbTk/TextButton.hh | |||
@@ -56,7 +56,8 @@ public: | |||
56 | 56 | ||
57 | void exposeEvent(XExposeEvent &event); | 57 | void exposeEvent(XExposeEvent &event); |
58 | 58 | ||
59 | void renderForeground(FbDrawable &drawable); | 59 | //void renderForeground(FbDrawable &drawable); |
60 | void renderForeground(FbWindow &win, FbDrawable &drawable); | ||
60 | 61 | ||
61 | FbTk::Justify justify() const { return m_justify; } | 62 | FbTk::Justify justify() const { return m_justify; } |
62 | const BiDiString &text() const { return m_text; } | 63 | const BiDiString &text() const { return m_text; } |
@@ -65,7 +66,6 @@ public: | |||
65 | unsigned int textWidth() const; | 66 | unsigned int textWidth() const; |
66 | int bevel() const { return m_bevel; } | 67 | int bevel() const { return m_bevel; } |
67 | 68 | ||
68 | void renderForeground(FbWindow &win, FbDrawable &drawable); | ||
69 | 69 | ||
70 | protected: | 70 | protected: |
71 | virtual void drawText(int x_offset, int y_offset, FbDrawable *drawable_override); | 71 | virtual void drawText(int x_offset, int y_offset, FbDrawable *drawable_override); |