| 1 |
//--------------------------------------------------------------------- |
|---|
| 2 |
/** |
|---|
| 3 |
Luigi is an OpenGL based GUI library. |
|---|
| 4 |
|
|---|
| 5 |
The goal is to provide a simple but flexible way to add a simple |
|---|
| 6 |
GUI to an OpenGL program. |
|---|
| 7 |
|
|---|
| 8 |
Luigi supports themes and input adaptors. Themes give you a way |
|---|
| 9 |
to customize the look of GUI. Input adaptors do the work of |
|---|
| 10 |
taking events from the OS or from a windowing toolkit like GLD, and |
|---|
| 11 |
turning them into something Luigi can use. |
|---|
| 12 |
*/ |
|---|
| 13 |
//--------------------------------------------------------------------- |
|---|
| 14 |
/* |
|---|
| 15 |
luigi/gui.d -- main import file for 'luigi' user interface library. |
|---|
| 16 |
version 0.5, December 3, 2006 |
|---|
| 17 |
|
|---|
| 18 |
Copyright (C) 2006 William V. Baxter III |
|---|
| 19 |
|
|---|
| 20 |
This software is provided 'as-is', without any express or implied |
|---|
| 21 |
warranty. In no event will the authors be held liable for any |
|---|
| 22 |
damages arising from the use of this software. |
|---|
| 23 |
|
|---|
| 24 |
Permission is granted to anyone to use this software for any |
|---|
| 25 |
purpose, including commercial applications, and to alter it and |
|---|
| 26 |
redistribute it freely, subject to the following restrictions: |
|---|
| 27 |
|
|---|
| 28 |
1. The origin of this software must not be misrepresented; you must |
|---|
| 29 |
not claim that you wrote the original software. If you use this |
|---|
| 30 |
software in a product, an acknowledgment in the product |
|---|
| 31 |
documentation would be appreciated but is not required. |
|---|
| 32 |
|
|---|
| 33 |
2. Altered source versions must be plainly marked as such, and must |
|---|
| 34 |
not be misrepresented as being the original software. |
|---|
| 35 |
3. This notice may not be removed or altered from any source distribution. |
|---|
| 36 |
|
|---|
| 37 |
William Baxter wbaxter@gmail.com |
|---|
| 38 |
*/ |
|---|
| 39 |
//---------------------------------------------------------------------------- |
|---|
| 40 |
/* |
|---|
| 41 |
WARNING: Because of how overloading works in D (at least as of DMD 0.176), |
|---|
| 42 |
******************************************* |
|---|
| 43 |
** ALL SETTERS MUST COME BEFORE GETTERS ** |
|---|
| 44 |
******************************************* |
|---|
| 45 |
I.e. |
|---|
| 46 |
|
|---|
| 47 |
int value(int newval) |
|---|
| 48 |
|
|---|
| 49 |
should always appear before |
|---|
| 50 |
|
|---|
| 51 |
int value() |
|---|
| 52 |
|
|---|
| 53 |
Otherwise, signal.connect(value) will silently connect to the |
|---|
| 54 |
getter rather than the setter. |
|---|
| 55 |
*/ |
|---|
| 56 |
module luigi.gui; |
|---|
| 57 |
|
|---|
| 58 |
import luigi.opengl; |
|---|
| 59 |
import luigi.base; |
|---|
| 60 |
import luigi.event; |
|---|
| 61 |
import luigi.font; |
|---|
| 62 |
import luigi.theme; |
|---|
| 63 |
static import luigi.themes.std; |
|---|
| 64 |
import luigi.arranger; |
|---|
| 65 |
import luigi.adapter; |
|---|
| 66 |
//import sslot.signal; |
|---|
| 67 |
import luigi.signalobj; |
|---|
| 68 |
|
|---|
| 69 |
//import adapter = luigi.adapter.glfw; |
|---|
| 70 |
|
|---|
| 71 |
import drawsys = luigi.gldraw; |
|---|
| 72 |
|
|---|
| 73 |
import math = std.math; |
|---|
| 74 |
import string = std.string; |
|---|
| 75 |
import std.ctype : isprint; |
|---|
| 76 |
import std.uni : isUniAlpha; |
|---|
| 77 |
static import std.path; |
|---|
| 78 |
static import std.file; |
|---|
| 79 |
|
|---|
| 80 |
// Just for debug? |
|---|
| 81 |
import std.stdio : writefln; |
|---|
| 82 |
|
|---|
| 83 |
|
|---|
| 84 |
/** |
|---|
| 85 |
* The singleton master object. |
|---|
| 86 |
* Holds GUI globals including the theme, the input adapter, and keeps track |
|---|
| 87 |
* of all the top-level Frames and Overlays. |
|---|
| 88 |
*/ |
|---|
| 89 |
class Luigi |
|---|
| 90 |
{ |
|---|
| 91 |
/// Return the singleton instance |
|---|
| 92 |
static Luigi opCall() |
|---|
| 93 |
{ |
|---|
| 94 |
static Luigi instance = null; |
|---|
| 95 |
if (!instance) instance = new Luigi; |
|---|
| 96 |
return instance; |
|---|
| 97 |
} |
|---|
| 98 |
|
|---|
| 99 |
/// Return the singleton instance |
|---|
| 100 |
static Luigi inst() { |
|---|
| 101 |
return Luigi(); |
|---|
| 102 |
} |
|---|
| 103 |
|
|---|
| 104 |
/// Set the theme |
|---|
| 105 |
Theme theme(Theme th) { return m_theme=th; } |
|---|
| 106 |
/// Get the current theme |
|---|
| 107 |
Theme theme() { |
|---|
| 108 |
if (!m_theme) { |
|---|
| 109 |
m_theme = new luigi.themes.std.StdTheme(); |
|---|
| 110 |
} |
|---|
| 111 |
return m_theme; |
|---|
| 112 |
} |
|---|
| 113 |
|
|---|
| 114 |
/// Add a location where themes should look for resource files |
|---|
| 115 |
/// (e.g. images, textures, etc. The built-in themes look for some files |
|---|
| 116 |
/// relative to the luigi base directory, so you may need to add it to |
|---|
| 117 |
/// your path.) The current directory '.' is the only thing on the resource path |
|---|
| 118 |
/// by default. |
|---|
| 119 |
void add_resource_location(char[] path) { |
|---|
| 120 |
char[] workp = path.dup; |
|---|
| 121 |
if (workp[$-1..$] != std.path.sep) { workp ~= std.path.sep; } |
|---|
| 122 |
m_resource_path ~= workp; |
|---|
| 123 |
} |
|---|
| 124 |
/// Add a location where themes should look for resource files to the beginning of |
|---|
| 125 |
/// the resource path. |
|---|
| 126 |
/// (e.g. images, textures, etc. The built-in themes look for some files |
|---|
| 127 |
/// relative to the luigi base directory, so you may need to add it to |
|---|
| 128 |
/// your path.) The current directory '.' is the only thing on the resource path |
|---|
| 129 |
/// by default. |
|---|
| 130 |
void prepend_resource_location(char[] path) { |
|---|
| 131 |
char[] workp = path.dup; |
|---|
| 132 |
if (workp[$-1..$] != std.path.sep) { workp ~= std.path.sep; } |
|---|
| 133 |
m_resource_path = workp ~ m_resource_path; |
|---|
| 134 |
} |
|---|
| 135 |
/// Get the current resource path. This is a list of paths where Luigi |
|---|
| 136 |
/// should look for resource files like images. |
|---|
| 137 |
char[][] resource_path() { |
|---|
| 138 |
return m_resource_path; |
|---|
| 139 |
} |
|---|
| 140 |
/// Finds the specified resource file on the resource path. This could be |
|---|
| 141 |
/// an image or anything else. Used to find theme textures internally. |
|---|
| 142 |
/// A list of names passed in will treated as alternative names for the same |
|---|
| 143 |
/// resource file and searched for in the order provided. |
|---|
| 144 |
/// Returns null if the resource could not be located on the resource path. |
|---|
| 145 |
char[] find_resource_location(char[][] name...) { |
|---|
| 146 |
foreach(char[] aname; name) { |
|---|
| 147 |
foreach(char[] p; m_resource_path) { |
|---|
| 148 |
char[] fullpath = p ~ aname; |
|---|
| 149 |
if (std.file.exists(fullpath)) { |
|---|
| 150 |
return fullpath; |
|---|
| 151 |
} |
|---|
| 152 |
} |
|---|
| 153 |
} |
|---|
| 154 |
return null; |
|---|
| 155 |
} |
|---|
| 156 |
|
|---|
| 157 |
/** Add a top level overlay. |
|---|
| 158 |
This is called automatically by Overlay, so typically there is no |
|---|
| 159 |
reason for users to call it themselves. |
|---|
| 160 |
*/ |
|---|
| 161 |
void add_overlay(Overlay ov) { |
|---|
| 162 |
m_guis ~= ov; |
|---|
| 163 |
|
|---|
| 164 |
if (!m_inputsys) { |
|---|
| 165 |
throw new GUIException("Must set Luigi().adapter before creating Overlays"); |
|---|
| 166 |
} |
|---|
| 167 |
|
|---|
| 168 |
// add in top-level input hooks |
|---|
| 169 |
with (m_inputsys) { |
|---|
| 170 |
addSysKeyCallback(&ov.on_sys_key); |
|---|
| 171 |
addSysMouseButtonCallback(&ov.on_sys_mouse_button) ; |
|---|
| 172 |
addMouseMoveCallback(&ov.on_sys_mouse_move); |
|---|
| 173 |
addMouseWheelCallback(&ov.on_sys_mouse_wheel); |
|---|
| 174 |
addWindowSizeCallback(&ov.on_sys_window_size); |
|---|
| 175 |
addWindowCloseCallback(&ov.on_sys_window_close); |
|---|
| 176 |
} |
|---|
| 177 |
} |
|---|
| 178 |
|
|---|
| 179 |
/** Returns the size of the specified window */ |
|---|
| 180 |
Size get_window_size(WindowHandle win) { |
|---|
| 181 |
return m_inputsys.get_window_size(win); |
|---|
| 182 |
} |
|---|
| 183 |
|
|---|
| 184 |
/** Set the current input adapter */ |
|---|
| 185 |
void adapter(InputAdapter inputsys) { |
|---|
| 186 |
m_inputsys = inputsys; |
|---|
| 187 |
} |
|---|
| 188 |
/** Get the current input adapter */ |
|---|
| 189 |
InputAdapter adapter() { |
|---|
| 190 |
return m_inputsys; |
|---|
| 191 |
} |
|---|
| 192 |
|
|---|
| 193 |
|
|---|
| 194 |
|
|---|
| 195 |
private: |
|---|
| 196 |
|
|---|
| 197 |
this() { |
|---|
| 198 |
add_resource_location("."); |
|---|
| 199 |
} |
|---|
| 200 |
|
|---|
| 201 |
~this() { |
|---|
| 202 |
} |
|---|
| 203 |
|
|---|
| 204 |
Overlay[] m_guis; |
|---|
| 205 |
InputAdapter m_inputsys; |
|---|
| 206 |
Theme m_theme; |
|---|
| 207 |
char[][] m_resource_path; |
|---|
| 208 |
} |
|---|
| 209 |
|
|---|
| 210 |
|
|---|
| 211 |
/// Add a user-level input callback functions and delegates |
|---|
| 212 |
void add_key_callback(KeyEventFn cb) {Luigi().adapter.addKeyCallback(cb);} |
|---|
| 213 |
void add_key_callback(KeyEventDg cb) {Luigi().adapter.addKeyCallback(cb);} |
|---|
| 214 |
void add_mouse_button_callback(MouseButtonEventFn cb) {Luigi().adapter.addMouseButtonCallback(cb); } |
|---|
| 215 |
void add_mouse_button_callback(MouseButtonEventDg cb) {Luigi().adapter.addMouseButtonCallback(cb);} |
|---|
| 216 |
void add_mouse_move_callback(MouseMoveEventFn cb) {Luigi().adapter.addMouseMoveCallback(cb);} |
|---|
| 217 |
void add_mouse_move_callback(MouseMoveEventDg cb) {Luigi().adapter.addMouseMoveCallback(cb);} |
|---|
| 218 |
void add_mouse_wheel_callback(MouseWheelEventFn cb) {Luigi().adapter.addMouseWheelCallback(cb);} |
|---|
| 219 |
void add_mouse_wheel_callback(MouseWheelEventDg cb) {Luigi().adapter.addMouseWheelCallback(cb);} |
|---|
| 220 |
void add_window_size_callback(WindowSizeEventFn cb) {Luigi().adapter.addWindowSizeCallback(cb);} |
|---|
| 221 |
void add_window_size_callback(WindowSizeEventDg cb) {Luigi().adapter.addWindowSizeCallback(cb);} |
|---|
| 222 |
void add_window_close_callback(WindowCloseEventFn cb) {Luigi().adapter.addWindowCloseCallback(cb);} |
|---|
| 223 |
void add_window_close_callback(WindowCloseEventDg cb) {Luigi().adapter.addWindowCloseCallback(cb);} |
|---|
| 224 |
/// Add a system-level input callback function or delegate -- Use with caution! |
|---|
| 225 |
void add_sys_key_callback(KeyEventFn cb) {Luigi().adapter.addSysKeyCallback(cb);} |
|---|
| 226 |
void add_sys_key_callback(KeyEventDg cb) {Luigi().adapter.addSysKeyCallback(cb);} |
|---|
| 227 |
void add_sys_mouse_button_callback(MouseButtonEventFn cb) {Luigi().adapter.addSysMouseButtonCallback(cb); } |
|---|
| 228 |
void add_sys_mouse_button_callback(MouseButtonEventDg cb) {Luigi().adapter.addSysMouseButtonCallback(cb); } |
|---|
| 229 |
|
|---|
| 230 |
|
|---|
| 231 |
/// Remove installed callbacks |
|---|
| 232 |
void remove_key_callback(KeyEventFn cb) {Luigi().adapter.removeKeyCallback(cb);} |
|---|
| 233 |
void remove_key_callback(KeyEventDg cb) {Luigi().adapter.removeKeyCallback(cb);} |
|---|
| 234 |
void remove_mouse_button_callback(MouseButtonEventFn cb) {Luigi().adapter.removeMouseButtonCallback(cb); } |
|---|
| 235 |
void remove_mouse_button_callback(MouseButtonEventDg cb) {Luigi().adapter.removeMouseButtonCallback(cb); } |
|---|
| 236 |
void remove_mouse_move_callback(MouseMoveEventFn cb) {Luigi().adapter.removeMouseMoveCallback(cb);} |
|---|
| 237 |
void remove_mouse_move_callback(MouseMoveEventDg cb) {Luigi().adapter.removeMouseMoveCallback(cb);} |
|---|
| 238 |
void remove_mouse_wheel_callback(MouseWheelEventFn cb) {Luigi().adapter.removeMouseWheelCallback(cb);} |
|---|
| 239 |
void remove_mouse_wheel_callback(MouseWheelEventDg cb) {Luigi().adapter.removeMouseWheelCallback(cb);} |
|---|
| 240 |
void remove_window_size_callback(WindowSizeEventFn cb) {Luigi().adapter.removeWindowSizeCallback(cb);} |
|---|
| 241 |
void remove_window_size_callback(WindowSizeEventDg cb) {Luigi().adapter.removeWindowSizeCallback(cb);} |
|---|
| 242 |
void remove_window_close_callback(WindowCloseEventFn cb) {Luigi().adapter.removeWindowCloseCallback(cb);} |
|---|
| 243 |
void remove_window_close_callback(WindowCloseEventDg cb) {Luigi().adapter.removeWindowCloseCallback(cb);} |
|---|
| 244 |
void remove_sys_key_callback(KeyEventFn cb) {Luigi().adapter.removeSysKeyCallback(cb);} |
|---|
| 245 |
void remove_sys_key_callback(KeyEventDg cb) {Luigi().adapter.removeSysKeyCallback(cb);} |
|---|
| 246 |
void remove_sys_mouse_button_callback(MouseButtonEventFn cb) {Luigi().adapter.removeSysMouseButtonCallback(cb); } |
|---|
| 247 |
void remove_sys_mouse_button_callback(MouseButtonEventDg cb) {Luigi().adapter.removeSysMouseButtonCallback(cb); } |
|---|
| 248 |
|
|---|
| 249 |
|
|---|
| 250 |
|
|---|
| 251 |
|
|---|
| 252 |
/** |
|---|
| 253 |
* Represents a real OS window used exclusively by Luigi. |
|---|
| 254 |
* If the GUI is drawn on top of the app's GL window, use Overlay instead. |
|---|
| 255 |
*/ |
|---|
| 256 |
class Frame |
|---|
| 257 |
{ |
|---|
| 258 |
// maybe later -- GLFW doesn't have a way to make more than one windowframe. |
|---|
| 259 |
// SDL doesn't either. |
|---|
| 260 |
// And maybe this should just be Window, because e.g. GLUT has sub-windows that can be |
|---|
| 261 |
// treated basically as top level windows. |
|---|
| 262 |
|
|---|
| 263 |
private: |
|---|
| 264 |
WindowHandle m_winhandle; |
|---|
| 265 |
} |
|---|
| 266 |
|
|---|
| 267 |
template WidgetMixin() |
|---|
| 268 |
{ |
|---|
| 269 |
alias typeof(this) WidgetType; |
|---|
| 270 |
static assert(is(WidgetType:luigi.gui.Widget), |
|---|
| 271 |
"WidgetMixin should be derived from Widget"); |
|---|
| 272 |
|
|---|
| 273 |
/** |
|---|
| 274 |
This function is a convenient way to add a newly constructed widget |
|---|
| 275 |
to an arranger while passing along extra necessary arguments. |
|---|
| 276 |
|
|---|
| 277 |
It is always possible to add the widget directy to the parent's arranger using |
|---|
| 278 |
the arranger.add(), but this way lets you construct the widget, add it to the |
|---|
| 279 |
arranger with arguments, and assign it to a variable of the derived type all |
|---|
| 280 |
in one line. |
|---|
| 281 |
|
|---|
| 282 |
For example: |
|---|
| 283 |
Button b = arranger_add(new Button(parent, "Name"), Arranger.Left, Arranger.Top); |
|---|
| 284 |
|
|---|
| 285 |
Returns: A pointer to 'this', with the proper derived type. |
|---|
| 286 |
*/ |
|---|
| 287 |
WidgetType arranged_(VArg...)(VArg varg) |
|---|
| 288 |
{ |
|---|
| 289 |
//static assert(is(T:Widget), "arranger_add requires a Widget as argument 0"); |
|---|
| 290 |
if (parent && parent.arranger && !parent.arranger.auto_add) { |
|---|
| 291 |
parent.arranger.add(this, varg); |
|---|
| 292 |
} |
|---|
| 293 |
return this; |
|---|
| 294 |
} |
|---|
| 295 |
} |
|---|
| 296 |
|
|---|
| 297 |
template PanelMixin() |
|---|
| 298 |
{ |
|---|
| 299 |
alias typeof(this) PanelType; |
|---|
| 300 |
static assert(is(PanelType:luigi.gui.Panel), |
|---|
| 301 |
"PanelMixin should be derived from Panel"); |
|---|
| 302 |
|
|---|
| 303 |
/** Add the widget to this panel. |
|---|
| 304 |
* |
|---|
| 305 |
* This method returns the widget passed in with its full derived type, |
|---|
| 306 |
* so add_widget can be used like: |
|---|
| 307 |
* --------- |
|---|
| 308 |
* auto b = border_panel.add_widget(new Button("Clicky")); |
|---|
| 309 |
* --------- |
|---|
| 310 |
* and the resulting b will have type Button. |
|---|
| 311 |
* |
|---|
| 312 |
* See_Also: add_arranged, Panel.add, Arranger.add |
|---|
| 313 |
*/ |
|---|
| 314 |
W add_widget(W)(W widget) |
|---|
| 315 |
{ |
|---|
| 316 |
static assert(is(W:luigi.gui.Widget), "add_widget requires a Widget as argument"); |
|---|
| 317 |
add(widget); |
|---|
| 318 |
return widget; |
|---|
| 319 |
} |
|---|
| 320 |
|
|---|
| 321 |
/** Add the widget to this panel and also to the panel's arranger |
|---|
| 322 |
* with arguments. |
|---|
| 323 |
* |
|---|
| 324 |
* This method returns the widget passed in with its full derived type, |
|---|
| 325 |
* so add_arranged can be used like: |
|---|
| 326 |
* --------- |
|---|
| 327 |
* auto b = border_panel.add_arranged(new Button("Clicky"), Region.East); |
|---|
| 328 |
* --------- |
|---|
| 329 |
* and the resulting b will have type Button. |
|---|
| 330 |
* |
|---|
| 331 |
* See_Also: add_widget, Panel.add, Arranger.add |
|---|
| 332 |
*/ |
|---|
| 333 |
W add_arranged(W, Varg...)(W widget, Varg args) |
|---|
| 334 |
{ |
|---|
| 335 |
static assert(is(W:luigi.gui.Widget), |
|---|
| 336 |
"add_arranged requires a Widget as argument"); |
|---|
| 337 |
add(widget); |
|---|
| 338 |
assert(arranger, "add_arranged called with no arranger set"); |
|---|
| 339 |
if (arranger && !arranger.auto_add) arranger.add(widget, args); |
|---|
| 340 |
return widget; |
|---|
| 341 |
} |
|---|
| 342 |
} |
|---|
| 343 |
|
|---|
| 344 |
/** |
|---|
| 345 |
* The base class for any GUI entity that occupies space on the screen. |
|---|
| 346 |
*/ |
|---|
| 347 |
class Widget : Arrangeable |
|---|
| 348 |
{ |
|---|
| 349 |
mixin WidgetMixin; |
|---|
| 350 |
|
|---|
| 351 |
// Some handy aliases everyone should have |
|---|
| 352 |
// This makes it so you can refer to these inside a Widget subclass |
|---|
| 353 |
// without worry |
|---|
| 354 |
alias luigi.base.Size Size; |
|---|
| 355 |
alias luigi.base.Rect Rect; |
|---|
| 356 |
alias luigi.base.Point Point; |
|---|
| 357 |
|
|---|
| 358 |
|
|---|
| 359 |
this() {} |
|---|
| 360 |
|
|---|
| 361 |
void add(Widget child) { |
|---|
| 362 |
throw new GUIException("Attempt to add child to a Widget"); |
|---|
| 363 |
} |
|---|
| 364 |
|
|---|
| 365 |
void draw() { |
|---|
| 366 |
// Default is to let the theme draw it. |
|---|
| 367 |
if (shown) |
|---|
| 368 |
Luigi().theme.draw(this); |
|---|
| 369 |
} |
|---|
| 370 |
|
|---|
| 371 |
//------------------------------------------------------------------------ |
|---|
| 372 |
// Default implementation of Arrangeable interface |
|---|
| 373 |
override Size minimum_size(Size bounds) { |
|---|
| 374 |
return Luigi().theme.minimum_size(this, bounds); |
|---|
| 375 |
} |
|---|
| 376 |
override Size preferred_size(Size bounds) { |
|---|
| 377 |
return Luigi().theme.preferred_size(this, bounds); |
|---|
| 378 |
} |
|---|
| 379 |
override void set_rect(Rect s) { m_rect = s; } |
|---|
| 380 |
override void set_position(Point p) { m_rect.x = p.x; m_rect.y = p.y; } |
|---|
| 381 |
override void set_size(Size sz) { m_rect.width = sz.width; m_rect.height = sz.height; } |
|---|
| 382 |
override void arrange() { /* nothing to do */ } |
|---|
| 383 |
|
|---|
| 384 |
|
|---|
| 385 |
/** Return the arranger used to arrange this widget's chilrend */ |
|---|
| 386 |
Arranger arranger() { return m_arranger; } |
|---|
| 387 |
|
|---|
| 388 |
/** Set the arranger used to arrange this widget's chilren */ |
|---|
| 389 |
Arranger arranger(Arranger a) |
|---|
| 390 |
{ |
|---|
| 391 |
if (a==m_arranger) return m_arranger; |
|---|
| 392 |
m_arranger = a; |
|---|
| 393 |
m_arranger.set_rect(m_rect); |
|---|
| 394 |
return m_arranger; |
|---|
| 395 |
} |
|---|
| 396 |
|
|---|
| 397 |
/** Return the arranger used to arrange this widget */ |
|---|
| 398 |
Arranger arranged_by() { |
|---|
| 399 |
if (parent) return parent.arranger; |
|---|
| 400 |
return null; |
|---|
| 401 |
} |
|---|
| 402 |
|
|---|
| 403 |
/** Return the items rectangle. |
|---|
| 404 |
The rectangle coordinates are relative to the upper left corner of this |
|---|
| 405 |
widget's parent. |
|---|
| 406 |
*/ |
|---|
| 407 |
Rect rect() { return m_rect; } |
|---|
| 408 |
// Rect rect(Rect r) { return m_rect; } // use set rect! |
|---|
| 409 |
|
|---|
| 410 |
|
|---|
| 411 |
void enable(bool isEnabled=true) { return m_enabled = isEnabled; } |
|---|
| 412 |
void disable() { m_enabled = false; } |
|---|
| 413 |
bool enabled() { return m_enabled; } |
|---|
| 414 |
bool disabled() { return !m_enabled; } |
|---|
| 415 |
|
|---|
| 416 |
void show() { m_shown = true; } |
|---|
| 417 |
void hide() { m_shown = false; } |
|---|
| 418 |
bool shown(bool show_) { return m_shown = show_; } |
|---|
| 419 |
bool shown() { return m_shown; } |
|---|
| 420 |
void toggle_shown() { m_shown = !m_shown; } |
|---|
| 421 |
|
|---|
| 422 |
/** Return the parent of this item or null if it has no parent. |
|---|
| 423 |
*/ |
|---|
| 424 |
Widget parent() { return m_parent; } |
|---|
| 425 |
|
|---|
| 426 |
/** Return the list of items that are parented to this one. |
|---|
| 427 |
Panels are the base type for all items with children. |
|---|
| 428 |
*/ |
|---|
| 429 |
Widget[] children() { return null; } |
|---|
| 430 |
|
|---|
| 431 |
/** Transforms the given point from window coordinates into |
|---|
| 432 |
the widget's coordinates. In widget coordinates, |
|---|
| 433 |
(rect.x,rect.y) is the upper left corner of the widget. |
|---|
| 434 |
*/ |
|---|
| 435 |
void transform_window_to_widget(inout Point winp) |
|---|
| 436 |
{ |
|---|
| 437 |
Widget w = this.parent; |
|---|
| 438 |
while( w ) |
|---|
| 439 |
{ |
|---|
| 440 |
winp.x -= w.m_rect.x; |
|---|
| 441 |
winp.y -= w.m_rect.y; |
|---|
| 442 |
w = w.parent; |
|---|
| 443 |
} |
|---|
| 444 |
} |
|---|
| 445 |
|
|---|
| 446 |
/** Return the item after this one in the tab traversal order */ |
|---|
| 447 |
Widget next_item() { |
|---|
| 448 |
Panel p = cast(Panel)parent; |
|---|
| 449 |
if (p) { return p.sibling_after(this); } |
|---|
| 450 |
return null; |
|---|
| 451 |
} |
|---|
| 452 |
/** Return the item before this one in the tab traversal order */ |
|---|
| 453 |
Widget prev_item() { |
|---|
| 454 |
Panel p = cast(Panel)parent; |
|---|
| 455 |
if (p) { return p.sibling_before(this); } |
|---|
| 456 |
return null; |
|---|
| 457 |
} |
|---|
| 458 |
|
|---|
| 459 |
/** Find the widget at the root of this item's hierarchy */ |
|---|
| 460 |
Widget get_root() { |
|---|
| 461 |
Widget r = this; |
|---|
| 462 |
while ( r.parent ) r = r.parent; |
|---|
| 463 |
return r; |
|---|
| 464 |
} |
|---|
| 465 |
|
|---|
| 466 |
bool is_grabbing_mouse() { |
|---|
| 467 |
Widget R = get_root(); |
|---|
| 468 |
return R._get_mouse_grabber() is this; |
|---|
| 469 |
} |
|---|
| 470 |
bool grab_mouse() { |
|---|
| 471 |
Widget R = get_root(); |
|---|
| 472 |
return R._set_mouse_grabber(this); |
|---|
| 473 |
} |
|---|
| 474 |
bool release_mouse() { |
|---|
| 475 |
Widget R = get_root(); |
|---|
| 476 |
return R._release_mouse_grabber(this); |
|---|
| 477 |
} |
|---|
| 478 |
|
|---|
| 479 |
|
|---|
| 480 |
protected bool _set_mouse_grabber(Widget w) |
|---|
| 481 |
{ |
|---|
| 482 |
assert(0, "Widgets don't grab the mouse"); |
|---|
| 483 |
return false; |
|---|
| 484 |
} |
|---|
| 485 |
protected bool _release_mouse_grabber(Widget w) |
|---|
| 486 |
{ |
|---|
| 487 |
assert(0, "Widgets don't grab the mouse"); |
|---|
| 488 |
return false; |
|---|
| 489 |
} |
|---|
| 490 |
protected Widget _get_mouse_grabber() |
|---|
| 491 |
{ |
|---|
| 492 |
assert(0, "Widgets don't grab the mouse"); |
|---|
| 493 |
return null; |
|---|
| 494 |
} |
|---|
| 495 |
|
|---|
| 496 |
//----------------- FOCUS METHODS -------------------------- |
|---|
| 497 |
|
|---|
| 498 |
/** Returns whether this item has the keyboard focus. |
|---|
| 499 |
*/ |
|---|
| 500 |
bool focused() |
|---|
| 501 |
in { get_root()._focus_sanity_check(); } body |
|---|
| 502 |
{ |
|---|
| 503 |
return m_focused; |
|---|
| 504 |
} |
|---|
| 505 |
|
|---|
| 506 |
bool focusable(bool onOff) |
|---|
| 507 |
in { get_root()._focus_sanity_check(); } body |
|---|
| 508 |
{ |
|---|
| 509 |
if (m_focusable == onOff) |
|---|
| 510 |
// no change |
|---|
| 511 |
return m_focusable; |
|---|
| 512 |
|
|---|
| 513 |
if (!onOff && m_focused) { |
|---|
| 514 |
// we were focused but we're being made non-focusable |
|---|
| 515 |
// Try to focus the next guy before turning our focus off. |
|---|
| 516 |
focus_next(); |
|---|
| 517 |
if (m_focused) // it didn't work! maybe we're the only item! |
|---|
| 518 |
_unfocus_rup(); |
|---|
| 519 |
} |
|---|
| 520 |
m_focusable = onOff; |
|---|
| 521 |
return m_focusable; |
|---|
| 522 |
} |
|---|
| 523 |
|
|---|
| 524 |
bool focusable() |
|---|
| 525 |
in { get_root()._focus_sanity_check(); } body |
|---|
| 526 |
{ |
|---|
| 527 |
return m_focusable && m_enabled; |
|---|
| 528 |
} |
|---|
| 529 |
|
|---|
| 530 |
/** Finds the keyboard focus of this widget's hierarchy. */ |
|---|
| 531 |
Widget get_focus() |
|---|
| 532 |
in { get_root()._focus_sanity_check(); } body |
|---|
| 533 |
{ |
|---|
| 534 |
Widget R = get_root(); |
|---|
| 535 |
Widget w = R._find_focus_rdown(); |
|---|
| 536 |
return w; |
|---|
| 537 |
} |
|---|
| 538 |
|
|---|
| 539 |
protected Widget _find_focus_rdown() |
|---|
| 540 |
{ |
|---|
| 541 |
writefln("Focus rdown widget"); |
|---|
| 542 |
// Shouldn't usually get here, unless we have no parent |
|---|
| 543 |
assert(parent==null, "I shouldn't even *be* here today."); |
|---|
| 544 |
return this; |
|---|
| 545 |
} |
|---|
| 546 |
|
|---|
| 547 |
/** Sets the keyboard focus of this widget's hierarchy to newfocus. |
|---|
| 548 |
* If newfocus is null any current focus item in this widget tree |
|---|
| 549 |
* will be unfocused. |
|---|
| 550 |
*/ |
|---|
| 551 |
bool set_focus(Widget newfocus) |
|---|
| 552 |
in { get_root()._focus_sanity_check(); } body |
|---|
| 553 |
{ |
|---|
| 554 |
if (newfocus) { |
|---|
| 555 |
if (!newfocus.focusable) return false; |
|---|
| 556 |
if (newfocus.m_focused) return true; |
|---|
| 557 |
} |
|---|
| 558 |
|
|---|
| 559 |
// unfocus the current guy first |
|---|
| 560 |
Widget oldFocus = get_focus(); |
|---|
| 561 |
if (oldFocus && !oldFocus._can_lose_focus()) { |
|---|
| 562 |
return false; |
|---|
| 563 |
} |
|---|
| 564 |
else if (oldFocus) { |
|---|
| 565 |
oldFocus._unfocus_rup(); |
|---|
| 566 |
} |
|---|
| 567 |
|
|---|
| 568 |
// Stop here if there's no new focus item |
|---|
| 569 |
if (!newfocus) return true; |
|---|
| 570 |
|
|---|
| 571 |
// now give focus to the new guy. |
|---|
| 572 |
// Set his m_focused flag and percolate m_childFocused flags up the chain. |
|---|
| 573 |
_FocusEvent ev; |
|---|
| 574 |
ev.previous = oldFocus; |
|---|
| 575 |
newfocus.on_focus(&ev); |
|---|
| 576 |
if (!ev.alive) { |
|---|
| 577 |
newfocus.m_focused = true; |
|---|
| 578 |
if (newfocus.parent) newfocus.parent._set_child_focused_rup(); |
|---|
| 579 |
} |
|---|
| 580 |
return newfocus.m_focused; |
|---|
| 581 |
} |
|---|
| 582 |
|
|---|
| 583 |
|
|---|
| 584 |
/** Set the keyboard focus to this item. |
|---|
| 585 |
Returns true if successful, false otherwise. |
|---|
| 586 |
*/ |
|---|
| 587 |
bool focus() |
|---|
| 588 |
in { get_root()._focus_sanity_check(); } body |
|---|
| 589 |
{ |
|---|
| 590 |
return set_focus(this); |
|---|
| 591 |
} |
|---|
| 592 |
|
|---|
| 593 |
protected void _set_child_focused_rup() { |
|---|
| 594 |
// recursively set child focus on all parents |
|---|
| 595 |
assert(false, "recursive focus only for Panel-derived classes."); |
|---|
| 596 |
} |
|---|
| 597 |
|
|---|
| 598 |
protected void _unfocus_rup() { |
|---|
| 599 |
// recursively unfocus this item and all parents |
|---|
| 600 |
m_focused = false; |
|---|
| 601 |
if (parent) parent._unfocus_rup(); |
|---|
| 602 |
} |
|---|
| 603 |
|
|---|
| 604 |
protected Widget _find_first_focusable_rdown() { |
|---|
| 605 |
if (focusable) { |
|---|
| 606 |
return this; |
|---|
| 607 |
} |
|---|
| 608 |
return null; |
|---|
| 609 |
} |
|---|
| 610 |
protected Widget _find_last_focusable_rdown() { |
|---|
| 611 |
if (focusable) { |
|---|
| 612 |
return this; |
|---|
| 613 |
} |
|---|
| 614 |
return null; |
|---|
| 615 |
} |
|---|
| 616 |
protected Widget _find_next_focusable_rdown(inout bool found_focus) |
|---|
| 617 |
{ |
|---|
| 618 |
if (m_focused) { found_focus = true; } |
|---|
| 619 |
return null; |
|---|
| 620 |
} |
|---|
| 621 |
protected Widget _find_prev_focusable_rdown(inout bool found_focus) |
|---|
| 622 |
{ |
|---|
| 623 |
if (m_focused) { found_focus = true; } |
|---|
| 624 |
return null; |
|---|
| 625 |
} |
|---|
| 626 |
|
|---|
| 627 |
protected int _focus_sanity_check(int c=0) { |
|---|
| 628 |
return c + (m_focused?1:0); |
|---|
| 629 |
} |
|---|
| 630 |
|
|---|
| 631 |
|
|---|
| 632 |
/** Moves focus to the next item in this widget's hierarchy, or |
|---|
| 633 |
to the first item if nothing is currently focused. |
|---|
| 634 |
Return the newly focused item. |
|---|
| 635 |
*/ |
|---|
| 636 |
Widget focus_next() |
|---|
| 637 |
{ |
|---|
| 638 |
Widget w = get_focus(); |
|---|
| 639 |
Widget R = get_root(); |
|---|
| 640 |
if (!w) { |
|---|
| 641 |
w = R._find_first_focusable_rdown(); |
|---|
| 642 |
} |
|---|
| 643 |
else { |
|---|
| 644 |
bool code = false; |
|---|
| 645 |
w = R._find_next_focusable_rdown(code); |
|---|
| 646 |
if (!w) { |
|---|
| 647 |
w = R._find_first_focusable_rdown(); |
|---|
| 648 |
} |
|---|
| 649 |
} |
|---|
| 650< |
|---|