Shorten SJME_THREAD_CONVENTION to sjme_attrThreadCall; Corrections for 32-bit Windows...
[SquirrelJME.git] / nanocoat / lib / scritchui / scritchComponent.c
blob1679dbc11373b0f1ff063d622e4bf6862968ced5
1 /* -*- Mode: C; indent-tabs-mode: t; tab-width: 4 -*-
2 // ---------------------------------------------------------------------------
3 // SquirrelJME
4 // Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
5 // ---------------------------------------------------------------------------
6 // SquirrelJME is under the Mozilla Public License Version 2.0.
7 // See license.mkd for licensing and copyright information.
8 // -------------------------------------------------------------------------*/
10 #include <string.h>
11 #include <stdio.h>
13 #include "lib/scritchui/core/core.h"
14 #include "lib/scritchui/scritchuiTypes.h"
15 #include "sjme/debug.h"
17 static sjme_errorCode sjme_scritchui_baseInputListenerMouse(
18 sjme_attrInNotNull sjme_scritchui inState,
19 sjme_attrInNotNull sjme_scritchui_uiComponent inComponent,
20 sjme_attrInNotNull const sjme_scritchinput_event* inEvent)
22 sjme_scritchui_listener_input* infoUser;
23 sjme_scritchui_uiMouseState* currentMouse;
24 sjme_scritchui_uiMouseState* logicalMouse;
25 sjme_scritchinput_event emit;
26 sjme_jint buttonChange, shift;
27 sjme_jboolean pressed;
29 if (inState == NULL || inComponent == NULL || inEvent == NULL)
30 return SJME_ERROR_NULL_ARGUMENTS;
32 /* We are adjusting with multiple mouse states. */
33 currentMouse = &inComponent->state.mouse[0];
34 logicalMouse = &inComponent->state.mouse[1];
36 /* Get callback information. */
37 infoUser = &SJME_SCRITCHUI_LISTENER_USER(inComponent, input);
39 /* Always copy modifiers over. */
40 logicalMouse->mouseModifiers = currentMouse->mouseModifiers;
42 /* Has there been a change to buttons? */
43 if (currentMouse->mouseButtons != logicalMouse->mouseButtons)
45 /* Store new position, if valid. */
46 if (currentMouse->mouseX != 0)
47 logicalMouse->mouseX = currentMouse->mouseX;
48 if (currentMouse->mouseY != 0)
49 logicalMouse->mouseY = currentMouse->mouseY;
51 /* Determine the buttons that changed. */
52 buttonChange = (currentMouse->mouseButtons ^
53 logicalMouse->mouseButtons);
55 /* If anything changed, go through each button. */
56 for (shift = 0; buttonChange != 0 && shift <= 31; shift++)
58 /* Button did not change? */
59 int mask = (1 << shift);
60 if ((buttonChange & mask) == 0)
61 continue;
63 /* We are changing this, so flip to new state. */
64 logicalMouse->mouseButtons ^= mask;
66 /* Is this being pressed or released? */
67 pressed = ((logicalMouse->mouseButtons & mask) != 0);
69 /* Setup event. */
70 memset(&emit, 0, sizeof(emit));
71 if (pressed)
72 emit.type = SJME_SCRITCHINPUT_TYPE_MOUSE_BUTTON_PRESSED;
73 else
74 emit.type = SJME_SCRITCHINPUT_TYPE_MOUSE_BUTTON_RELEASED;
75 emit.time = inEvent->time;
76 emit.data.mouseButton.button = shift + 1;
77 emit.data.mouseButton.buttonMask = logicalMouse->mouseButtons;
78 emit.data.mouseButton.modifiers = logicalMouse->mouseModifiers;
79 emit.data.mouseButton.x = logicalMouse->mouseX;
80 emit.data.mouseButton.y = logicalMouse->mouseY;
82 #if 0
83 /* Debug. */
84 sjme_message("Mouse Button: %s %d %08x (%d, %d) [sh=%d, bc=%08x]",
85 (pressed ? "pressed" : "released"),
86 emit.data.mouseButton.buttonMask,
87 emit.data.mouseButton.button,
88 emit.data.mouseButton.x,
89 emit.data.mouseButton.y,
90 shift, buttonChange);
91 #endif
93 /* Emit a button event. */
94 if (infoUser->callback != NULL)
95 return infoUser->callback(inState, inComponent, &emit);
99 /* Has there been a change of position? */
100 if (currentMouse->mouseX != logicalMouse->mouseX ||
101 currentMouse->mouseY != logicalMouse->mouseY)
103 /* Store new position. */
104 logicalMouse->mouseX = currentMouse->mouseX;
105 logicalMouse->mouseY = currentMouse->mouseY;
107 /* Setup event. */
108 memset(&emit, 0, sizeof(emit));
109 emit.type = SJME_SCRITCHINPUT_TYPE_MOUSE_MOTION;
110 emit.time = inEvent->time;
111 emit.data.mouseMotion.buttonMask = logicalMouse->mouseButtons;
112 emit.data.mouseMotion.modifiers = logicalMouse->mouseModifiers;
113 emit.data.mouseMotion.x = logicalMouse->mouseX;
114 emit.data.mouseMotion.y = logicalMouse->mouseY;
116 /* Debug. */
117 #if 0
118 sjme_message("Mouse Motion: %08x (%d, %d)",
119 emit.data.mouseMotion.buttonMask,
120 emit.data.mouseMotion.x,
121 emit.data.mouseMotion.y);
122 #endif
124 /* Emit a motion event. */
125 if (infoUser->callback != NULL)
126 return infoUser->callback(inState, inComponent, &emit);
129 /* Nothing changed, we want to reduce duplication. */
130 return SJME_ERROR_NONE;
133 static sjme_errorCode sjme_scritchui_baseInputListener(
134 sjme_attrInNotNull sjme_scritchui inState,
135 sjme_attrInNotNull sjme_scritchui_uiComponent inComponent,
136 sjme_attrInNotNull const sjme_scritchinput_event* inEvent)
138 sjme_scritchui_listener_input* infoUser;
139 sjme_scritchui_uiMouseState* currentMouse;
140 sjme_scritchui_uiMouseState* logicalMouse;
141 sjme_scritchinput_event clone;
142 sjme_jint bit;
143 sjme_jboolean isPressEvent;
145 if (inState == NULL || inComponent == NULL || inEvent == NULL)
146 return SJME_ERROR_NULL_ARGUMENTS;
148 /* Get callback information. */
149 infoUser = &SJME_SCRITCHUI_LISTENER_USER(inComponent, input);
151 /* Clone event data for normalization. */
152 memmove(&clone, inEvent, sizeof(clone));
154 /* We are only adjusting the current mouse state. */
155 currentMouse = &inComponent->state.mouse[0];
156 logicalMouse = &inComponent->state.mouse[1];
158 /* Mouse events. */
159 if (clone.type == SJME_SCRITCHINPUT_TYPE_MOUSE_MOTION ||
160 clone.type == SJME_SCRITCHINPUT_TYPE_MOUSE_BUTTON_PRESSED ||
161 clone.type == SJME_SCRITCHINPUT_TYPE_MOUSE_BUTTON_RELEASED)
163 /* Is this a press/release event? */
164 isPressEvent = SJME_JNI_FALSE;
165 if (clone.type == SJME_SCRITCHINPUT_TYPE_MOUSE_BUTTON_PRESSED ||
166 clone.type == SJME_SCRITCHINPUT_TYPE_MOUSE_BUTTON_RELEASED)
167 isPressEvent = SJME_JNI_TRUE;
169 /* Pull from logical unless position is set as some */
170 /* GUIs do not report position when buttons are pressed. This is */
171 /* the case where mouse buttons are technically keyboard keys. */
172 /* Motion always copies, however. */
173 if (isPressEvent && clone.data.mouseButton.x == 0)
174 currentMouse->mouseX = logicalMouse->mouseX;
175 else
176 currentMouse->mouseX = clone.data.mouseButton.x;
178 if (isPressEvent && clone.data.mouseButton.y == 0)
179 currentMouse->mouseY = logicalMouse->mouseY;
180 else
181 currentMouse->mouseY = clone.data.mouseButton.y;
183 /* Set modifiers, if unsupported for this event type then pull */
184 /* from the last logical modifier state. */
185 if (clone.data.mouseMotion.modifiers ==
186 SJME_SCRITCHINPUT_MODIFIER_UNSUPPORTED)
187 currentMouse->mouseModifiers = logicalMouse->mouseModifiers;
188 else
189 currentMouse->mouseModifiers = clone.data.mouseMotion.modifiers;
191 /* If no buttons are down, pull from logical. Otherwise, use the */
192 /* mask from the event. Some GUIs will not pass buttons during */
193 /* motion events, but will for normal press/release. */
194 if (clone.data.mouseMotion.buttonMask == 0)
195 currentMouse->mouseButtons = logicalMouse->mouseButtons;
196 else
197 currentMouse->mouseButtons = clone.data.mouseMotion.buttonMask;
199 /* Either set or clear the mouse button bit, if known. */
200 if (isPressEvent && clone.data.mouseButton.button != 0)
202 bit = (1 << (clone.data.mouseButton.button - 1));
203 if (clone.type == SJME_SCRITCHINPUT_TYPE_MOUSE_BUTTON_PRESSED)
204 currentMouse->mouseButtons |= bit;
205 else
206 currentMouse->mouseButtons &= ~bit;
209 /* Forward to handler to further clean up. */
210 return sjme_scritchui_baseInputListenerMouse(inState, inComponent,
211 &clone);
214 /* Normalize key. */
215 else if (clone.type == SJME_SCRITCHINPUT_TYPE_KEY_PRESSED ||
216 clone.type == SJME_SCRITCHINPUT_TYPE_KEY_RELEASED)
218 /* If any lowercase keys were passed, make them capitalized. */
219 bit = clone.data.key.code;
220 if (bit >= 'a' && bit <= 'z')
221 clone.data.key.code = 'A' + (bit - 'a');
224 /* Forward to callback! */
225 if (infoUser->callback != NULL)
226 return infoUser->callback(inState, inComponent, &clone);
227 return SJME_ERROR_NONE;
230 static sjme_errorCode sjme_scritchui_basePaintListener(
231 sjme_attrInNotNull sjme_scritchui inState,
232 sjme_attrInNotNull sjme_scritchui_uiComponent inComponent,
233 sjme_attrInNotNull sjme_scritchui_pencil g,
234 sjme_attrInPositive sjme_jint sw,
235 sjme_attrInPositive sjme_jint sh,
236 sjme_attrInValue sjme_jint special)
238 sjme_errorCode error;
239 sjme_scritchui_uiPaintable paint;
240 sjme_scritchui_listener_paint* infoUser;
241 sjme_scritchui_paintListenerFunc callback;
243 if (inState == NULL || inComponent == NULL || g == NULL)
244 return SJME_ERROR_NULL_ARGUMENTS;
246 /* Not something we can paint? */
247 paint = NULL;
248 if (sjme_error_is(error = inState->intern->getPaintable(inState,
249 inComponent, &paint)) || paint == NULL)
250 return sjme_error_defaultOr(error,
251 SJME_ERROR_INVALID_ARGUMENT);
253 /* Base info. */
254 infoUser = &SJME_SCRITCHUI_LISTENER_USER(paint, paint);
256 /* No actual paint listener? */
257 callback = infoUser->callback;
258 if (callback == NULL)
260 error = SJME_ERROR_NO_LISTENER;
261 goto fail_noListener;
264 /* Forward to callback. */
265 sjme_atomic_sjme_jint_set(&paint->inPaint, 1);
266 error = callback(inState, inComponent, g, sw, sh, special);
268 #if defined(SJME_CONFIG_DEBUG)
269 /* Error? */
270 if (sjme_error_is(error))
271 sjme_message("Paint failed: %d", error);
272 #endif
274 /* No longer painting. */
275 sjme_atomic_sjme_jint_set(&paint->inPaint, 0);
277 /* Success or failure! */
278 fail_noListener:
279 paint->lastError = error;
280 return error;
283 static sjme_errorCode sjme_scritchui_core_baseActivateListener(
284 sjme_attrInNotNull sjme_scritchui inState,
285 sjme_attrInNotNull sjme_scritchui_uiComponent inComponent)
287 sjme_errorCode error;
288 sjme_scritchui_listener_activate* info;
289 sjme_scritchui_activateListenerFunc callback;
291 if (inState == NULL || inComponent == NULL)
292 return SJME_ERROR_NULL_ARGUMENTS;
294 /* Base info. */
295 info = &SJME_SCRITCHUI_LISTENER_USER(inComponent, activate);
297 /* Call user handler, if there is one */
298 callback = info->callback;
299 if (callback != NULL)
300 if (sjme_error_is(error = callback(inState, inComponent)))
301 return sjme_error_default(error);
303 /* Success! */
304 return SJME_ERROR_NONE;
307 static sjme_errorCode sjme_scritchui_core_baseSizeListener(
308 sjme_attrInNotNull sjme_scritchui inState,
309 sjme_attrInNotNull sjme_scritchui_uiComponent inComponent,
310 sjme_attrInPositiveNonZero sjme_jint newWidth,
311 sjme_attrInPositiveNonZero sjme_jint newHeight)
313 sjme_errorCode error;
314 sjme_scritchui_listener_size* info;
315 sjme_scritchui_sizeListenerFunc callback;
317 if (inState == NULL || inComponent == NULL)
318 return SJME_ERROR_NULL_ARGUMENTS;
320 /* Base info. */
321 info = &SJME_SCRITCHUI_LISTENER_USER(inComponent, size);
323 /* Call user handler, if there is one */
324 callback = info->callback;
325 if (callback != NULL)
326 if (sjme_error_is(error = callback(inState, inComponent,
327 newWidth, newHeight)))
328 return sjme_error_default(error);
330 /* Schedule repaint, ignore any errors. */
331 inState->api->componentRepaint(inState, inComponent,
332 0, 0, INT32_MAX, INT32_MAX);
334 /* Success! */
335 return SJME_ERROR_NONE;
338 static sjme_errorCode sjme_scritchui_core_baseVisibleListener(
339 sjme_attrInNotNull sjme_scritchui inState,
340 sjme_attrInNotNull sjme_scritchui_uiComponent inComponent,
341 sjme_attrInValue sjme_jboolean fromVisible,
342 sjme_attrInValue sjme_jboolean toVisible)
344 sjme_scritchui_listener_visible* infoUser;
345 sjme_jboolean wasVisible;
346 sjme_jboolean wasUserVisible;
348 if (inState == NULL || inComponent == NULL)
349 return SJME_ERROR_NULL_ARGUMENTS;
351 /* Has core visibility changed? We do not want to spam visibility */
352 /* changes to our components if there is no point to it. */
353 wasVisible = inComponent->state.isVisible;
354 if (toVisible != wasVisible)
356 /* Update visibility state. */
357 inComponent->state.isVisible = toVisible;
360 /* There may be a delay before a listener is set for a user listener */
361 /* so wait until that occurs. */
362 wasUserVisible = inComponent->state.isUserVisible;
363 if (toVisible != wasUserVisible)
365 /* Send to callback accordingly, if one is set. */
366 infoUser = &SJME_SCRITCHUI_LISTENER_USER(inComponent, visible);
367 if (infoUser->callback != NULL)
369 /* Update state. */
370 inComponent->state.isUserVisible = toVisible;
372 return infoUser->callback(inState, inComponent,
373 wasVisible, toVisible);
377 /* Success! */
378 return SJME_ERROR_NONE;
382 * Belayed repainting.
384 * @param anything The input component.
385 * @return On any error.
386 * @since 2024/04/26
388 static sjme_thread_result sjme_attrThreadCall
389 sjme_scritchui_core_componentRepaintBelay(
390 sjme_attrInNullable sjme_thread_parameter anything)
392 sjme_errorCode error;
393 sjme_scritchui inState;
394 sjme_scritchui_uiComponent inComponent;
395 sjme_scritchui_uiPaintable paint;
396 sjme_scritchui_rect rect;
398 if (anything == NULL)
399 return SJME_THREAD_RESULT(SJME_ERROR_NULL_ARGUMENTS);
401 /* Recover component and state. */
402 inComponent = (sjme_scritchui_uiComponent)anything;
403 inState = inComponent->common.state;
405 /* Only certain types are paintable. */
406 paint = NULL;
407 if (sjme_error_is(error = inState->intern->getPaintable(inState,
408 inComponent, &paint)) || paint == NULL)
409 return SJME_THREAD_RESULT(sjme_error_default(error));
411 /* Call paint now. */
412 rect = paint->belayRect;
413 error = inState->impl->componentRepaint(inState, inComponent,
414 rect.s.x, rect.s.y, rect.d.width, rect.d.height);
415 return SJME_THREAD_RESULT(error);
418 sjme_errorCode sjme_scritchui_core_componentFocusGrab(
419 sjme_attrInNotNull sjme_scritchui inState,
420 sjme_attrInNotNull sjme_scritchui_uiComponent inComponent)
422 sjme_errorCode error;
424 if (inState == NULL || inComponent == NULL)
425 return SJME_ERROR_NULL_ARGUMENTS;
427 /* Not implemented? */
428 if (inState->impl->componentFocusGrab == NULL)
429 return sjme_error_notImplemented(0);
431 /* For Windows, keyboard input happens on the window itself and not */
432 /* the component, so we need to refer back. */
433 if (sjme_error_is(error = inState->intern->bindFocus(inState,
434 inComponent, inComponent, SJME_JNI_TRUE)))
435 return sjme_error_default(error);
437 /* Direct forward. */
438 return inState->impl->componentFocusGrab(inState, inComponent);
441 sjme_errorCode sjme_scritchui_core_componentFocusHas(
442 sjme_attrInNotNull sjme_scritchui inState,
443 sjme_attrInNotNull sjme_scritchui_uiComponent inComponent,
444 sjme_attrOutNotNull sjme_jboolean* outHasFocus)
446 if (inState == NULL || inComponent == NULL || outHasFocus == NULL)
447 return SJME_ERROR_NULL_ARGUMENTS;
449 /* Not implemented? */
450 if (inState->impl->componentFocusHas == NULL)
451 return sjme_error_notImplemented(0);
453 /* Direct forward. */
454 return inState->impl->componentFocusHas(inState, inComponent, outHasFocus);
457 sjme_errorCode sjme_scritchui_core_componentGetParent(
458 sjme_attrInNotNull sjme_scritchui inState,
459 sjme_attrInNotNull sjme_scritchui_uiComponent inComponent,
460 sjme_attrOutNotNull sjme_scritchui_uiComponent* outParent)
462 if (inState == NULL || inComponent == NULL || outParent == NULL)
463 return SJME_ERROR_NULL_ARGUMENTS;
465 /* This is a simple read. */
466 *outParent = inComponent->parent;
467 return SJME_ERROR_NONE;
470 sjme_errorCode sjme_scritchui_core_componentPosition(
471 sjme_attrInNotNull sjme_scritchui inState,
472 sjme_attrInNotNull sjme_scritchui_uiComponent inComponent,
473 sjme_attrOutNullable sjme_jint* outX,
474 sjme_attrOutNullable sjme_jint* outY)
476 if (inState == NULL || inComponent == NULL ||
477 (outX == NULL && outY == NULL))
478 return SJME_ERROR_NULL_ARGUMENTS;
480 /* If there is native position information, use it. */
481 if (inState->impl->componentPosition != NULL)
482 return inState->impl->componentPosition(inState, inComponent,
483 outX, outY);
485 /* Otherwise, fallback to last known bounds. */
486 if (outX != NULL)
487 *outX = inComponent->bounds.s.x;
488 if (outY != NULL)
489 *outY = inComponent->bounds.s.y;
491 /* Success! */
492 return SJME_ERROR_NONE;
495 sjme_errorCode sjme_scritchui_core_componentRepaint(
496 sjme_attrInNotNull sjme_scritchui inState,
497 sjme_attrInNotNull sjme_scritchui_uiComponent inComponent,
498 sjme_attrInPositive sjme_jint x,
499 sjme_attrInPositive sjme_jint y,
500 sjme_attrInPositiveNonZero sjme_jint width,
501 sjme_attrInPositiveNonZero sjme_jint height)
503 sjme_errorCode error;
504 sjme_scritchui_uiPaintable paint;
505 sjme_scritchui_uiContainer container;
506 sjme_scritchui_uiComponent* subComponents;
507 sjme_jint i, n;
509 if (inState == NULL || inComponent == NULL)
510 return SJME_ERROR_NULL_ARGUMENTS;
512 /* Not implemented? */
513 if (inState->impl->componentRepaint == NULL)
514 return sjme_error_notImplemented(0);
516 /* Only certain types are paintable, ignore if requested. */
517 paint = NULL;
518 if (sjme_error_is(error = inState->intern->getPaintable(inState,
519 inComponent, &paint)) || paint == NULL)
521 /* An actual error? */
522 if (error != SJME_ERROR_INVALID_ARGUMENT)
523 return sjme_error_default(error);
525 #if 1
526 /* If this is a container, repaint all children. */
527 container = NULL;
528 if (sjme_error_is(error = inState->intern->getContainer(inState,
529 inComponent, &container)) || container == NULL)
530 return sjme_error_default(error);
532 /* Do not bother? */
533 if (container->components == NULL ||
534 container->components->length == 0)
535 return SJME_ERROR_NONE;
537 /* Allocate storage for components. */
538 n = container->components->length;
539 subComponents = sjme_alloca(sizeof(*subComponents) * n);
540 if (subComponents == NULL)
541 return SJME_ERROR_OUT_OF_MEMORY;
543 /* Get all components. */
544 memmove(subComponents, container->components->elements,
545 sizeof(*subComponents) * n);
547 /* Go through each and request repainting. */
548 for (i = 0; i < n; i++)
549 if (subComponents[i] != NULL)
550 if (sjme_error_is(error = inState->apiInThread
551 ->componentRepaint(inState, subComponents[i],
552 x, y, width, height)))
554 if (error != SJME_ERROR_INVALID_ARGUMENT)
555 return sjme_error_default(error);
557 #endif
559 /* Success! */
560 return SJME_ERROR_NONE;
563 /* Rather than failing, just normalize. */
564 if (x < 0)
565 x = 0;
566 if (y < 0)
567 y = 0;
568 if (width <= 0)
569 width = INT32_MAX;
570 if (height <= 0)
571 height = INT32_MAX;
573 /* If we are in a paint, we need to delay painting by a single frame */
574 /* otherwise the native UI might get stuck not repainting or end up */
575 /* in an infinite loop. */
576 if (sjme_atomic_sjme_jint_get(&paint->inPaint) != 0)
578 /* Store paint properties. */
579 paint->belayRect.s.x = x;
580 paint->belayRect.s.y = y;
581 paint->belayRect.d.width = width;
582 paint->belayRect.d.height = height;
584 /* Schedule for later, if it errors fall through to paint. */
585 if (!sjme_error_is(inState->api->loopExecuteLater(inState,
586 sjme_scritchui_core_componentRepaintBelay,
587 inComponent)))
588 return SJME_ERROR_NONE;
591 /* Forward. */
592 return inState->impl->componentRepaint(inState, inComponent,
593 x, y, width, height);
596 sjme_errorCode sjme_scritchui_core_componentRevalidate(
597 sjme_attrInNotNull sjme_scritchui inState,
598 sjme_attrInNotNull sjme_scritchui_uiComponent inComponent)
600 sjme_errorCode error;
602 if (inState == NULL || inComponent == NULL)
603 return SJME_ERROR_NULL_ARGUMENTS;
605 if (inState->impl->componentRevalidate == NULL)
606 return sjme_error_notImplemented(0);
608 /* Forward call. */
609 if (sjme_error_is(error = inState->impl->componentRevalidate(inState,
610 inComponent)))
611 return sjme_error_default(error);
613 /* Success! */
614 return SJME_ERROR_NONE;
617 sjme_errorCode sjme_scritchui_core_componentSetActivateListener(
618 sjme_attrInNotNull sjme_scritchui inState,
619 sjme_attrInNotNull sjme_scritchui_uiComponent inComponent,
620 SJME_SCRITCHUI_SET_LISTENER_ARGS(activate))
622 if (inState == NULL || inComponent == NULL)
623 return SJME_ERROR_NULL_ARGUMENTS;
625 return inState->intern->setSimpleListener(
626 inState,
627 (sjme_scritchui_listener_void*)&SJME_SCRITCHUI_LISTENER_USER(
628 inComponent, activate),
629 (sjme_scritchui_voidListenerFunc)inListener,
630 copyFrontEnd);
633 sjme_errorCode sjme_scritchui_core_componentSetInputListener(
634 sjme_attrInNotNull sjme_scritchui inState,
635 sjme_attrInNotNull sjme_scritchui_uiComponent inComponent,
636 SJME_SCRITCHUI_SET_LISTENER_ARGS(input))
638 sjme_errorCode error;
639 sjme_scritchui_inputListenerFunc coreListener;
641 if (inState == NULL || inComponent == NULL)
642 return SJME_ERROR_NULL_ARGUMENTS;
644 /* Can only be set for panels. */
645 if (inComponent->common.type != SJME_SCRITCHUI_TYPE_PANEL)
646 return SJME_ERROR_INVALID_ARGUMENT;
648 /* Using core input listener? */
649 coreListener = NULL;
650 if (inListener != NULL)
651 coreListener = sjme_scritchui_baseInputListener;
653 /* Set core listener events which is forwarded to for handling. */
654 if (inState->impl->componentSetInputListener != NULL)
656 if (sjme_error_is(error = inState->impl->
657 componentSetInputListener(inState, inComponent,
658 coreListener, NULL)))
659 return sjme_error_default(error);
662 /* Set one regardless, there might be another way to handle. */
663 else
665 if (sjme_error_is(error = inState->intern->setSimpleListener(
666 inState,
667 (sjme_scritchui_listener_void*)&SJME_SCRITCHUI_LISTENER_CORE(
668 inComponent, input),
669 (sjme_scritchui_voidListenerFunc)coreListener,
670 copyFrontEnd)))
671 return sjme_error_default(error);
674 /* Set user listener. */
675 return inState->intern->setSimpleListener(
676 inState,
677 (sjme_scritchui_listener_void*)&SJME_SCRITCHUI_LISTENER_USER(
678 inComponent, input),
679 (sjme_scritchui_voidListenerFunc)inListener,
680 copyFrontEnd);
683 sjme_errorCode sjme_scritchui_core_componentSetPaintListener(
684 sjme_scritchui inState,
685 sjme_scritchui_uiComponent inComponent,
686 SJME_SCRITCHUI_SET_LISTENER_ARGS(paint))
688 sjme_errorCode error;
689 sjme_scritchui_uiPaintable paint;
690 sjme_scritchui_listener_paint undo;
691 sjme_scritchui_listener_paint* infoCore;
692 sjme_scritchui_listener_paint* infoUser;
693 sjme_scritchui_paintListenerFunc coreCallback;
695 if (inState == NULL || inComponent == NULL)
696 return SJME_ERROR_NULL_ARGUMENTS;
698 /* Not supported? */
699 if (inState->impl->componentSetPaintListener == NULL)
700 return sjme_error_notImplemented(0);
702 /* Only certain types can be painted on. */
703 paint = NULL;
704 if (sjme_error_is(error = inState->intern->getPaintable(inState,
705 inComponent, &paint)) || paint == NULL)
706 return sjme_error_default(error);
708 /* Get listener information. */
709 infoUser = &SJME_SCRITCHUI_LISTENER_USER(paint, paint);
710 infoCore = &SJME_SCRITCHUI_LISTENER_CORE(paint, paint);
712 /* Copy data for undo. */
713 memmove(&undo, infoUser, sizeof(undo));
715 /* Set new listener data. */
716 if (sjme_error_is(error = inState->intern->setSimpleListener(
717 inState, (sjme_scritchui_listener_void*)infoUser,
718 (sjme_scritchui_voidListenerFunc)inListener, copyFrontEnd)))
719 return sjme_error_default(error);
721 /* Which core callback is being used? */
722 coreCallback = (inListener != NULL ? sjme_scritchui_basePaintListener :
723 NULL);
725 /* Is this callback changing? We need to set a new one! */
726 if (infoCore->callback != coreCallback)
728 if (sjme_error_is(error =
729 inState->impl->componentSetPaintListener(inState, inComponent,
730 coreCallback, NULL)))
731 goto fail_coreSet;
733 /* Set new listener data, if it was not changed. */
734 if (infoCore->callback != coreCallback)
735 if (sjme_error_is(error = inState->intern->setSimpleListener(
736 inState, (sjme_scritchui_listener_void*)infoCore,
737 (sjme_scritchui_voidListenerFunc)inListener,
738 copyFrontEnd)))
739 return sjme_error_default(error);
742 /* If there is a repaint handler, then run it but ignore any errors. */
743 if (inState->apiInThread->componentRepaint != NULL)
744 inState->apiInThread->componentRepaint(inState, inComponent,
745 0, 0, INT32_MAX, INT32_MAX);
747 /* Success! */
748 return SJME_ERROR_NONE;
750 fail_coreSet:
751 /* Undo change. */
752 memmove(infoUser, &undo, sizeof(undo));
754 return sjme_error_default(error);
757 sjme_errorCode sjme_scritchui_core_componentSetSizeListener(
758 sjme_attrInNotNull sjme_scritchui inState,
759 sjme_attrInNotNull sjme_scritchui_uiComponent inComponent,
760 SJME_SCRITCHUI_SET_LISTENER_ARGS(size))
762 if (inState == NULL || inComponent == NULL)
763 return SJME_ERROR_NULL_ARGUMENTS;
765 return inState->intern->setSimpleListener(
766 inState,
767 (sjme_scritchui_listener_void*)&SJME_SCRITCHUI_LISTENER_USER(
768 inComponent, size),
769 (sjme_scritchui_voidListenerFunc)inListener,
770 copyFrontEnd);
773 sjme_errorCode sjme_scritchui_core_componentSetValueUpdateListener(
774 sjme_attrInNotNull sjme_scritchui inState,
775 sjme_attrInNotNull sjme_scritchui_uiComponent inComponent,
776 SJME_SCRITCHUI_SET_LISTENER_ARGS(valueUpdate))
778 sjme_todo("Impl?");
779 return sjme_error_notImplemented(0);
782 sjme_errorCode sjme_scritchui_core_componentSetVisibleListener(
783 sjme_attrInNotNull sjme_scritchui inState,
784 sjme_attrInNotNull sjme_scritchui_uiComponent inComponent,
785 SJME_SCRITCHUI_SET_LISTENER_ARGS(visible))
787 if (inState == NULL || inComponent == NULL)
788 return SJME_ERROR_NULL_ARGUMENTS;
790 return inState->intern->setSimpleListener(
791 inState,
792 (sjme_scritchui_listener_void*)&SJME_SCRITCHUI_LISTENER_USER(
793 inComponent, visible),
794 (sjme_scritchui_voidListenerFunc)inListener,
795 copyFrontEnd);
798 sjme_errorCode sjme_scritchui_core_componentSize(
799 sjme_attrInNotNull sjme_scritchui inState,
800 sjme_attrInNotNull sjme_scritchui_uiComponent inComponent,
801 sjme_attrOutNullable sjme_jint* outWidth,
802 sjme_attrOutNullable sjme_jint* outHeight)
804 if (inState == NULL || inComponent == NULL ||
805 (outWidth == NULL && outHeight == NULL))
806 return SJME_ERROR_NULL_ARGUMENTS;
808 /* Not supported? */
809 if (inState->impl->componentSize == NULL)
810 return SJME_ERROR_NULL_ARGUMENTS;
812 /* Forward. */
813 return inState->impl->componentSize(inState, inComponent,
814 outWidth, outHeight);
817 sjme_errorCode sjme_scritchui_core_intern_getPaintable(
818 sjme_attrInNotNull sjme_scritchui inState,
819 sjme_attrInNotNull sjme_scritchui_uiComponent inComponent,
820 sjme_attrInOutNotNull sjme_scritchui_uiPaintable* outPaintable)
822 sjme_scritchui_uiPaintable paint;
824 if (inState == NULL || inComponent == NULL || outPaintable == NULL)
825 return SJME_ERROR_NULL_ARGUMENTS;
827 /* Only certain types can be painted on. */
828 switch (inComponent->common.type)
830 case SJME_SCRITCHUI_TYPE_PANEL:
831 paint = &((sjme_scritchui_uiPanel)inComponent)->paint;
832 break;
834 default:
835 return SJME_ERROR_INVALID_ARGUMENT;
838 /* Success! */
839 *outPaintable = paint;
840 return SJME_ERROR_NONE;
843 sjme_errorCode sjme_scritchui_core_intern_initCommon(
844 sjme_attrInNotNull sjme_scritchui inState,
845 sjme_attrInNotNull sjme_scritchui_uiCommon inCommon,
846 sjme_attrInValue sjme_jboolean postCreate,
847 sjme_attrInRange(0, SJME_NUM_SCRITCHUI_UI_TYPES)
848 sjme_scritchui_uiType uiType)
850 sjme_errorCode error;
851 sjme_alloc_link* link;
853 if (inState == NULL || inCommon == NULL)
854 return SJME_ERROR_NULL_ARGUMENTS;
856 /* Post-initialize? */
857 if (postCreate)
859 /* Currently nothing. */
862 /* Pre-initialize? */
863 else
865 /* Must be directly linked. */
866 link = NULL;
867 if (sjme_error_is(error = sjme_alloc_getLink(inCommon,
868 &link)) || link == NULL)
869 return sjme_error_defaultOr(error,
870 SJME_ERROR_INVALID_LINK);
872 /* Must be weak referenced. */
873 if (link->weak == NULL)
874 return SJME_ERROR_NOT_WEAK_REFERENCE;
876 /* Type must be valid. */
877 if (uiType <= SJME_SCRITCHUI_TYPE_RESERVED ||
878 uiType >= SJME_NUM_SCRITCHUI_UI_TYPES)
879 return SJME_ERROR_INVALID_ARGUMENT;
881 /* Set base properties. */
882 inCommon->state = inState;
883 inCommon->type = uiType;
886 /* Success! */
887 return SJME_ERROR_NONE;
890 sjme_errorCode sjme_scritchui_core_intern_initComponent(
891 sjme_attrInNotNull sjme_scritchui inState,
892 sjme_attrInNotNull sjme_scritchui_uiComponent inComponent,
893 sjme_attrInValue sjme_jboolean postCreate,
894 sjme_attrInRange(0, SJME_NUM_SCRITCHUI_UI_TYPES)
895 sjme_scritchui_uiType uiType)
897 sjme_errorCode error;
898 sjme_scritchui_uiPaintable paint;
899 sjme_scritchui_uiContainer container;
901 if (inState == NULL || inComponent == NULL)
902 return SJME_ERROR_NULL_ARGUMENTS;
904 /* Common initialize. */
905 if (sjme_error_is(error = inState->intern->initCommon(
906 inState, &inComponent->common, postCreate, uiType)))
907 return sjme_error_default(error);
909 /* Post-initialize? */
910 if (postCreate)
912 /* Install activate listener for activation events. */
913 if (inState->impl->componentSetActivateListener != NULL)
915 if (sjme_error_is(error =
916 inState->impl->componentSetActivateListener(inState,
917 inComponent,
918 sjme_scritchui_core_baseActivateListener,
919 NULL)))
920 return sjme_error_default(error);
922 else
924 /* Still set the activation listener because there might be */
925 /* implicit soft activates. */
926 SJME_SCRITCHUI_LISTENER_CORE(inComponent, activate)
927 .callback = sjme_scritchui_core_baseActivateListener;
930 /* Install size listener to emit repaints on resize. */
931 if (inState->impl->componentSetSizeListener != NULL)
932 if (sjme_error_is(error =
933 inState->impl->componentSetSizeListener(inState,
934 inComponent,
935 sjme_scritchui_core_baseSizeListener,
936 NULL)))
937 return sjme_error_default(error);
939 /* Set base visibility listener. */
940 if (inState->impl->componentSetVisibleListener != NULL)
942 if (sjme_error_is(error =
943 inState->impl->componentSetVisibleListener(inState,
944 inComponent,
945 sjme_scritchui_core_baseVisibleListener,
946 NULL)))
947 return sjme_error_default(error);
949 else
951 /* If there is no native support for listeners, still set it */
952 /* as we will handle visibility ourselves manually. */
953 SJME_SCRITCHUI_LISTENER_CORE(inComponent, visible)
954 .callback = sjme_scritchui_core_baseVisibleListener;
957 /* Common paintable base initialization. */
958 paint = NULL;
959 if (!sjme_error_is(error = inState->intern->getPaintable(
960 inState, inComponent, &paint)) &&
961 paint != NULL)
965 /* Common container base initialization. */
966 container = NULL;
967 if (!sjme_error_is(error = inState->intern->getContainer(
968 inState, inComponent, &container)) &&
969 container != NULL)
974 /* Pre-initialize? */
975 else
977 /* Make up a string based ID for the component. */
978 snprintf(inComponent->strId,
979 SJME_SCRITCHUI_UI_COMPONENT_ID_STRLEN - 1,
980 "sjme%p", inComponent);
983 /* Success! */
984 return SJME_ERROR_NONE;
987 sjme_errorCode sjme_scritchui_core_intern_setSimpleListener(
988 sjme_attrInNotNull sjme_scritchui inState,
989 sjme_attrInNotNull sjme_scritchui_listener_void* infoAny,
990 SJME_SCRITCHUI_SET_LISTENER_ARGS(void))
992 if (inState == NULL || infoAny == NULL)
993 return SJME_ERROR_NULL_ARGUMENTS;
995 /* Set new callback and copy any front-end data as needed. */
996 infoAny->callback = inListener;
997 if (inListener != NULL && copyFrontEnd != NULL)
998 memmove(&infoAny->frontEnd, copyFrontEnd,
999 sizeof(*copyFrontEnd));
1001 /* Clear old front end data for the listener if it was cleared. */
1002 if (inListener == NULL)
1003 memset(&infoAny->frontEnd, 0, sizeof(infoAny->frontEnd));
1005 /* Success! */
1006 return SJME_ERROR_NONE;
1009 sjme_errorCode sjme_scritchui_core_intern_updateVisibleComponent(
1010 sjme_attrInNotNull sjme_scritchui inState,
1011 sjme_attrInNotNull sjme_scritchui_uiComponent inComponent,
1012 sjme_attrInValue sjme_jboolean isVisible)
1014 sjme_scritchui_listener_visible* infoUser;
1016 if (inState == NULL || inComponent == NULL)
1017 return SJME_ERROR_NULL_ARGUMENTS;
1019 /* There always is a core interface! */
1020 infoUser = &SJME_SCRITCHUI_LISTENER_CORE(inComponent, visible);
1021 if (infoUser->callback != NULL)
1022 return infoUser->callback(inState, inComponent,
1023 inComponent->state.isVisible, isVisible);
1025 /* There was no callback, so just success. */
1026 return SJME_ERROR_NONE;