comparison mde/content/AStringContent.d @ 105:08651e8a8c51

Quit button, big changes to content system. Moved mde.gui.content to mde.content to reflect it's not only used by the gui. Split Content module into Content and AStringContent. New AContent and EventContent class. Callbacks are now generic and implemented in AContent. Renamed TextContent to StringContent and ValueContent to AStringContent.
author Diggory Hardy <diggory.hardy@gmail.com>
date Sat, 29 Nov 2008 12:36:39 +0000
parents
children fe061009029d
comparison
equal deleted inserted replaced
104:ee209602770d 105:08651e8a8c51
1 /* LICENSE BLOCK
2 Part of mde: a Modular D game-oriented Engine
3 Copyright © 2007-2008 Diggory Hardy
4
5 This program is free software: you can redistribute it and/or modify it under the terms
6 of the GNU General Public License as published by the Free Software Foundation, either
7 version 2 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
10 without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11 See the GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License
14 along with this program. If not, see <http://www.gnu.org/licenses/>. */
15
16 /** The content system − string-based editable content.
17 */
18 module mde.content.AStringContent;
19 public import mde.content.Content;
20
21 //FIXME: efficient conversions? Need to dup result when formatting a string anyway?
22 import Int = tango.text.convert.Integer;
23 import Float = tango.text.convert.Float;
24 import derelict.sdl.keysym;
25
26 debug {
27 import tango.util.log.Log : Log, Logger;
28 private Logger logger;
29 static this () {
30 logger = Log.getLogger ("mde.content.AStringContent");
31 }
32 }
33
34 // Used by Options
35 template ContentN(T) {
36 static if (is(T == bool)) {
37 const char[] ContentN = "BoolContent";
38 } else static if (is(T == int)) {
39 const char[] ContentN = "IntContent";
40 } else static if (is(T == double)) {
41 const char[] ContentN = "DoubleContent";
42 } else static if (is(T == char[])) {
43 const char[] ContentN = "StringContent";
44 } else
45 static assert (false, "No Content of type "~T.stringof);
46 }
47
48 /** Base class for content containing a simple value editable as text. All content types used by
49 * Options extend this class.
50 *
51 * All derived classes should have the following functions:
52 * ---
53 * void endEdit (); // Should convert sv and assign to self, then call endEvent
54 * // Used by Options:
55 * BoolContent changeCb (void delegate (char[] symbol,T value) cb); // The callback used by Options
56 * void assignNoCb (T val); // assign val, but without calling callbacks
57 * ---
58 * On any assignation (by this, assignNoCb, opAssign) the value should be converted to a string and
59 * assigned to sv, and pos should be clamped to [0,sv.length] (i.e. enforce pos <= sv.length). */
60 abstract class AStringContent : AContent
61 {
62 protected this (char[] symbol) {
63 super (symbol);
64 }
65
66 /// Get the text.
67 char[] toString (uint i) {
68 return i == 0 ? sv
69 : i == 1 ? name_
70 : i == 2 ? desc_
71 : null;
72 }
73
74 /** Acts on a keystroke and returns the new value.
75 *
76 * Supports one-line editing: left/right, home/end, backspace/delete. */
77 char[] keyStroke (ushort sym, char[] i) {
78 debug assert (i.length, "StringContent.keyStroke: no value (??)"); // impossible?
79 char k = *i;
80 if (k > 0x20) {
81 if (k == 0x7f) { // delete
82 size_t p = pos;
83 if (p < sv.length) ++p;
84 while (p < sv.length && (sv[p] & 0x80) && !(sv[p] & 0x40))
85 ++p;
86 sv = sv[0..pos] ~ sv[p..$];
87 } else { // insert character
88 char[] tail = sv[pos..$];
89 sv.length = sv.length + i.length;
90 size_t npos = pos+i.length;
91 if (tail) sv[npos..$] = tail.dup; // cannot assign with overlapping ranges
92 sv[pos..npos] = i;
93 pos = npos;
94 }
95 } else { // use sym; many keys output 0
96 if (sym == SDLK_BACKSPACE) { // backspace; k == 0x8
97 char[] tail = sv[pos..$];
98 if (pos) --pos;
99 while (pos && (sv[pos] & 0x80) && !(sv[pos] & 0x40))
100 --pos;
101 sv = sv[0..pos] ~ tail;
102 } else if (sym == SDLK_LEFT) {
103 if (pos) --pos;
104 while (pos && (sv[pos] & 0x80) && !(sv[pos] & 0x40))
105 --pos;
106 } else if (sym == SDLK_RIGHT) {
107 if (pos < sv.length) ++pos;
108 while (pos < sv.length && (sv[pos] & 0x80) && !(sv[pos] & 0x40))
109 ++pos;
110 } else if (sym == SDLK_HOME || sym == SDLK_UP) {
111 pos = 0;
112 } else if (sym == SDLK_END || sym == SDLK_DOWN) {
113 pos = sv.length;
114 } else
115 debug logger.trace ("Unhandled symbol: {}", sym);
116 }
117 return sv;
118 }
119
120 /// Get the character the edit cursor is in front of
121 size_t editIndex () {
122 size_t i = 0;
123 for (size_t p = 0; p < pos; ++p)
124 if (!(sv[p] & 0x80) || sv[p] & 0x40)
125 ++i;
126 return i;
127 }
128
129 /// Call after editing a string
130 void endEdit ();
131
132 protected:
133 char[] sv; // string of value; updated on assignment for displaying and editing
134 size_t pos; // editing position; used by keyStroke
135 }
136
137 class BoolContent : AStringContent
138 {
139 /** Create a content with _symbol name symbol. */
140 this (char[] symbol, bool val = false) {
141 assignNoCb (val);
142 super (symbol);
143 }
144
145 void assignNoCb (bool val) {
146 v = val;
147 sv = v ? "true" : "false";
148 if (pos > sv.length) pos = sv.length;
149 }
150 void opAssign (bool val) {
151 assignNoCb (val);
152 endEvent;
153 }
154 bool opCall () {
155 return v;
156 }
157 alias opCall opCast;
158
159 override void endEdit () {
160 v = sv && (sv[0] == 't' || sv[0] == 'T' || sv[0] == '1');
161 endEvent;
162 }
163
164 protected:
165 bool v;
166 }
167
168 /** Text content. */
169 class StringContent : AStringContent
170 {
171 this (char[] symbol, char[] val = null) {
172 v = val;
173 super (symbol);
174 }
175
176 void assignNoCb (char[] val) {
177 v = val;
178 if (pos > sv.length) pos = sv.length;
179 }
180 void opAssign (char[] val) {
181 assignNoCb (val);
182 endEvent;
183 }
184 char[] opCall () {
185 return v;
186 }
187 alias opCall opCast;
188
189 override void endEdit () {
190 endEvent;
191 }
192
193 protected:
194 alias sv v; // don't need separate v and sv in this case
195 }
196
197 /** Integer content. */
198 class IntContent : AStringContent
199 {
200 /** Create a content with _symbol name symbol. */
201 this (char[] symbol, int val = 0) {
202 assignNoCb (val);
203 super (symbol);
204 }
205
206 void assignNoCb (int val) {
207 v = val;
208 sv = Int.toString (v);
209 if (pos > sv.length) pos = sv.length;
210 }
211 void opAssign (int val) {
212 assignNoCb (val);
213 endEvent;
214 }
215 int opCall () {
216 return v;
217 }
218 alias opCall opCast;
219
220 override void endEdit () {
221 v = Int.toInt (sv);
222 endEvent;
223 }
224
225 protected:
226 int v;
227 }
228
229 /** Double content. */
230 class DoubleContent : AStringContent
231 {
232 /** Create a content with _symbol name symbol. */
233 this (char[] symbol, double val = 0) {
234 assignNoCb (val);
235 super (symbol);
236 }
237
238 void assignNoCb (double val) {
239 v = val;
240 sv = Float.toString (v);
241 if (pos > sv.length) pos = sv.length;
242 }
243 void opAssign (double val) {
244 assignNoCb (val);
245 endEvent;
246 }
247 double opCall () {
248 return v;
249 }
250 alias opCall opCast;
251
252 override void endEdit () {
253 v = Float.toFloat (sv);
254 endEvent;
255 }
256
257 protected:
258 double v;
259 }