kiwi
kiwi copied to clipboard
Implementing a message box
I'm currently writing my custom hello world example which consists of a frame with two buttons, one of which quits the program and the other one displaying a (modal) message box with a single OK-button for closing it. Ideally the latter would be a matter of using a convenience function, but that doesn't apper to exist yet. Currently I have two ideas how one could implement it:
- Draw a composite widget that is the child of the frame that looks like a message box, then write code for deleting/hiding it and invoke that on clicking the OK-button. I've done this currently, but struggle with the deletion part. Using
KW_DestroyWidget
is clearly not the right solution as that yields a SIGSEGV, setting the widget's geometry to an empty rectangle hides its frame, but leaves its child widgets still visible and drawn as children of the parent frame. - Create a new SDL2 window (and renderer?), create a new driver, create a new
KW_GUI
and draw the composite widget there. What I don't really understand for that approach is how one would modify the SDL2 event loop to deal with the new window as long as it's open.
Thoughts on this?
edit: I've found out that SDL2 provides SDL_ShowSimpleMessageBox
which is sort of OK as it has the desired behavior, but looks totally different from the rest of KiWi and doesn't update the parent window (leading to the infamous Windows dragging glitch).
Hi! KW_DestroyWidget
segfaulting is a bug I was not aware of, it should be fixed.
What you really need is a function to hide the widget (similar to KW_BlockWidgetInputEvents
) and make KW_PaintWidget
check that. It currently does not, but it should. Thanks for bringing this up.
To avoid painting children widgets outside their parent geometry (clipping them), try using KW_SetClipChildrenWidgets(frame, KW_TRUE)
. I am not really sure if it is going to work with a zero geometry (last I rememeber, clipping the window with a zero size rect was buggy), but you can try. Keep in mind this would be a workaround until the widget can be hidden properly and/or the destroy routine is fixed. By the way, do you have a stacktrace of it?
Your second alternative (a new window and renderer) may work for Desktop, but surely wouldn't for mobile. Also, I think it is overkill just for an editbox modal.
I also agree that KiWi needs convenience functions for message boxes and prompts, that's a feature to keep in the radar.
Using KW_SetClipChildrenWidgets
for the first approach appears to have no effect. I'll rewrite my current example in C and submit it here soonish.
The only reason I suggested the second approach is because it allows you to spawn a new OS-specific window (as opposed to a widget drawn in KiWi) and would allow for making it modal by nesting SDL event loops (or non-modal by doing event-processing and rendering sequentially in one event loop).
Program:
#include "KW_gui.h"
#include "KW_frame.h"
#include "KW_label.h"
#include "KW_button.h"
#include "KW_renderdriver_sdl2.h"
KW_Widget * messagebox;
void ok_clicked(KW_Widget * widget, int button) {
KW_DestroyWidget(messagebox, KW_TRUE);
}
int main(int argc, char ** argv) {
SDL_Init(SDL_INIT_EVERYTHING);
SDL_Renderer * renderer;
SDL_Window * window;
SDL_CreateWindowAndRenderer(320, 240, 0, &window, &renderer);
SDL_SetRenderDrawColor(renderer, 200, 150, 100, 1); /* pretty background */
KW_RenderDriver * driver = KW_CreateSDL2RenderDriver(renderer, window);
KW_Surface * set = KW_LoadSurface(driver, "tileset.png");
KW_GUI * gui = KW_Init(driver, set);
KW_Font * font = KW_LoadFont(driver, "Fontin-Regular.ttf", 12);
KW_SetFont(gui, font);
KW_Rect geometry = { x: 0, y: 0, w: 320, h: 240 };
KW_Widget * frame = KW_CreateFrame(gui, NULL, &geometry);
KW_Rect messagebox_geometry = { x: 0, y: 0, w: 192, h: 120 };
KW_RectCenterInParent(&geometry, &messagebox_geometry);
messagebox = KW_CreateFrame(gui, frame, &messagebox_geometry);
KW_Rect messagebox_label_geometry = { x: 0, y: 0, w: 192, h: 48 };
KW_Widget * messagebox_label = KW_CreateLabel(gui, messagebox, "Hello World!", &messagebox_label_geometry);
KW_Rect messagebox_button_geometry = { x: 120, y: 84, w: 48, h: 24 };
KW_Widget * messagebox_button = KW_CreateButton(gui, messagebox, "OK", &messagebox_button_geometry);
KW_AddWidgetMouseUpHandler(messagebox_button, ok_clicked);
while (!SDL_QuitRequested()) {
SDL_RenderClear(renderer);
KW_ProcessEvents(gui);
KW_Paint(gui);
SDL_Delay(1);
SDL_RenderPresent(renderer);
}
KW_Quit(gui);
KW_ReleaseSurface(driver, set);
KW_ReleaseFont(driver, font);
KW_ReleaseRenderDriver(driver);
SDL_Quit();
return 0;
}
gdb interaction:
(gdb) run
Starting program: /home/wasa/code/chicken/kiwi/c-examples/hello-world/hello-world
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
[New Thread 0x7ffff548e700 (LWP 10061)]
[New Thread 0x7fffe50d1700 (LWP 10062)]
Thread 1 "hello-world" received signal SIGSEGV, Segmentation fault.
KW_SetFocusedWidget (widget=0x21652f0) at /home/wasa/code/arch/pkgbuilds/kiwi-git/src/KiWi/src/KW_widget.c:209
209 count = gui->currentfocus->eventhandlers[KW_ON_FOCUSLOSE].count;
(gdb) bt
#0 KW_SetFocusedWidget (widget=0x21652f0) at /home/wasa/code/arch/pkgbuilds/kiwi-git/src/KiWi/src/KW_widget.c:209
#1 0x00007ffff78c2e97 in MouseReleased (gui=0x0, gui@entry=0x21337e0, mousex=<optimized out>, mousey=<optimized out>, button=<optimized out>)
at /home/wasa/code/arch/pkgbuilds/kiwi-git/src/KiWi/src/KW_eventwatcher.c:141
#2 0x00007ffff78c3172 in KW_ProcessEvents (gui=0x21337e0) at /home/wasa/code/arch/pkgbuilds/kiwi-git/src/KiWi/src/KW_eventwatcher.c:200
#3 0x000000000040109b in main (argc=1, argv=0x7fffffffe1a8) at hello-world.c:46
(gdb)
Ah. I'll look into these problems.
I've implemented KW_HideWidget
and KW_ShowWidget
and their respective effects. I've blocked input events if the widget is hidden, as I understand this behaviour is expected. Please let me have your input on this. It needs more testing, though, I think some events may still fire.
I'll look into the destroywidget bug later. From a quick look at your trace, I can tell the widget event handlers are not being removed after that widget is destroyed, there's no code to do that.
Cool, that works! Somehow it's still feeling off for me, probably because this message box looks very much unlike anything else I've seen and is transparent.
Code:
#include "KW_gui.h"
#include "KW_frame.h"
#include "KW_label.h"
#include "KW_button.h"
#include "KW_renderdriver_sdl2.h"
KW_Widget * messagebox;
void ok_clicked(KW_Widget * widget, int button) {
KW_HideWidget(messagebox);
}
void greet_clicked(KW_Widget * widget, int button) {
KW_ShowWidget(messagebox);
}
KW_bool quit = KW_FALSE;
void quit_clicked(KW_Widget * widget, int button) {
quit = KW_TRUE;
}
int main(int argc, char ** argv) {
SDL_Init(SDL_INIT_EVERYTHING);
SDL_Renderer * renderer;
SDL_Window * window;
SDL_CreateWindowAndRenderer(320, 240, 0, &window, &renderer);
SDL_SetRenderDrawColor(renderer, 200, 150, 100, 1); /* pretty background */
KW_RenderDriver * driver = KW_CreateSDL2RenderDriver(renderer, window);
KW_Surface * set = KW_LoadSurface(driver, "tileset.png");
KW_GUI * gui = KW_Init(driver, set);
KW_Font * font = KW_LoadFont(driver, "Fontin-Regular.ttf", 12);
KW_SetFont(gui, font);
KW_Rect geometry = { x: 0, y: 0, w: 320, h: 240 };
KW_Widget * frame = KW_CreateFrame(gui, NULL, &geometry);
KW_Rect greet_button_geometry = { x: 48, y: 144, w: 96, h: 48 };
KW_Widget * greet_button = KW_CreateButton(gui, frame, "Click Me!", &greet_button_geometry);
KW_AddWidgetMouseUpHandler(greet_button, greet_clicked);
KW_Rect quit_button_geometry = { x: 192, y: 144, w: 96, h: 48 };
KW_Widget * quit_button = KW_CreateButton(gui, frame, "Quit", &quit_button_geometry);
KW_AddWidgetMouseUpHandler(quit_button, quit_clicked);
KW_Rect messagebox_geometry = { x: 0, y: 0, w: 192, h: 120 };
KW_RectCenterInParent(&geometry, &messagebox_geometry);
messagebox = KW_CreateFrame(gui, frame, &messagebox_geometry);
KW_Rect messagebox_label_geometry = { x: 0, y: 0, w: 192, h: 48 };
KW_Widget * messagebox_label = KW_CreateLabel(gui, messagebox, "Hello World!", &messagebox_label_geometry);
KW_Rect messagebox_button_geometry = { x: 120, y: 84, w: 48, h: 24 };
KW_Widget * messagebox_button = KW_CreateButton(gui, messagebox, "OK", &messagebox_button_geometry);
KW_AddWidgetMouseUpHandler(messagebox_button, ok_clicked);
KW_HideWidget(messagebox);
while (!SDL_QuitRequested() && !quit) {
SDL_RenderClear(renderer);
KW_ProcessEvents(gui);
KW_Paint(gui);
SDL_Delay(1);
SDL_RenderPresent(renderer);
}
KW_Quit(gui);
KW_ReleaseSurface(driver, set);
KW_ReleaseFont(driver, font);
KW_ReleaseRenderDriver(driver);
SDL_Quit();
return 0;
}
Yeah it looks odd indeed, there is no depth. Tileset is the guilty here. Replace tileset.png with the one attached, it looks much better.
Nice work, by the way.
That helps, but if I were to use that tileset with the styleswitcher example, frames would look pretty weird as a frame is used there like a pane widget which must look flat to be used as visual groups. Perhaps it's time for extending the tileset for panes?
Other problems are that the message box is partially covering the other buttons (something that wouldn't happen if one had a much larger "screen") and that the message box isn't modal at all, you can for instance still click the quit button partially covered by it. Is it possible to block all widgets except the ones belonging to the message box?
Perhaps it's time for extending the tileset for panes?
It appears so. Maybe we could actually add styles for the frame, like raised, flat and sunken, and extend their tileset.
For blocking events, I'd suggest you put everything you want to block in an empty, fake widget and then block inputs for that widget.
#include "KW_gui.h"
#include "KW_frame.h"
#include "KW_label.h"
#include "KW_button.h"
#include "KW_renderdriver_sdl2.h"
KW_Widget * messagebox;
KW_Widget * fake;
void ok_clicked(KW_Widget * widget, int button) {
KW_HideWidget(messagebox);
KW_UnblockWidgetInputEvents(fake);
}
void greet_clicked(KW_Widget * widget, int button) {
KW_ShowWidget(messagebox);
KW_BlockWidgetInputEvents(fake);
}
KW_bool quit = KW_FALSE;
void quit_clicked(KW_Widget * widget, int button) {
quit = KW_TRUE;
}
int main(int argc, char ** argv) {
SDL_Init(SDL_INIT_EVERYTHING);
SDL_Renderer * renderer;
SDL_Window * window;
SDL_CreateWindowAndRenderer(320, 240, 0, &window, &renderer);
SDL_SetRenderDrawColor(renderer, 200, 150, 100, 1); /* pretty background */
KW_RenderDriver * driver = KW_CreateSDL2RenderDriver(renderer, window);
KW_Surface * set = KW_LoadSurface(driver, "tileset.png");
KW_GUI * gui = KW_Init(driver, set);
KW_Font * font = KW_LoadFont(driver, "Fontin-Regular.ttf", 12);
KW_SetFont(gui, font);
KW_Rect geometry = { x: 0, y: 0, w: 320, h: 240 };
KW_Widget * frame = KW_CreateFrame(gui, NULL, &geometry);
//this obviously needs its own KW_Create* functions, couldn't decide its name.
fake = KW_CreateWidget(gui, frame, KW_WIDGETTYPE_NONE, &geometry, NULL, NULL, NULL);
KW_Rect greet_button_geometry = { x: 48, y: 144, w: 96, h: 48 };
KW_Widget * greet_button = KW_CreateButton(gui, fake, "Click Me!", &greet_button_geometry);
KW_AddWidgetMouseUpHandler(greet_button, greet_clicked);
KW_Rect quit_button_geometry = { x: 192, y: 144, w: 96, h: 48 };
KW_Widget * quit_button = KW_CreateButton(gui, fake, "Quit", &quit_button_geometry);
KW_AddWidgetMouseUpHandler(quit_button, quit_clicked);
KW_Rect messagebox_geometry = { x: 0, y: 0, w: 192, h: 120 };
KW_RectCenterInParent(&geometry, &messagebox_geometry);
messagebox = KW_CreateFrame(gui, frame, &messagebox_geometry);
KW_Rect messagebox_label_geometry = { x: 0, y: 0, w: 192, h: 48 };
KW_Widget * messagebox_label = KW_CreateLabel(gui, messagebox, "Hello World!", &messagebox_label_geometry);
KW_Rect messagebox_button_geometry = { x: 120, y: 84, w: 48, h: 24 };
KW_Widget * messagebox_button = KW_CreateButton(gui, messagebox, "OK", &messagebox_button_geometry);
KW_AddWidgetMouseUpHandler(messagebox_button, ok_clicked);
KW_HideWidget(messagebox);
while (!SDL_QuitRequested() && !quit) {
SDL_RenderClear(renderer);
KW_ProcessEvents(gui);
KW_Paint(gui);
SDL_Delay(1);
SDL_RenderPresent(renderer);
}
KW_Quit(gui);
KW_ReleaseSurface(driver, set);
KW_ReleaseFont(driver, font);
KW_ReleaseRenderDriver(driver);
SDL_Quit();
return 0;
}
That does indeed look like a solution, but won't do as soon as there's some convenience function for opening a message box.
I'm thinking on a KW_Modal composite widget, that generates a widget covering the whole parent widget geometry (possibly using new tileset slot, allowing it to serve like a mask/shade), blocking all widget events below it. This widget would have a frame as children which will conform to the requested widget geometry (the mask wouldn't, it would always have the parent geometry - we can have functions to enable/disable this) and would paint a frame (a slot below/above the mask tiles). This widget would be the parent of the user widgets.
Does this sound like a good solution? Thoughts?
KW_Widget * KW_CreateModal(KW_Widget * parent, KW_Rect * geometry) {
// Create an empty widget, whose geometry = parent->geometry
// Create another widget, children of the above widget , using the passed in geometry
// return second widget
}
KW_Widget * parent; // Assume lots of widgets children of this
KW_Widget * modal = KW_CreateModal(parent, geometry);
// create widgets that would go into the modal widget
Hm, not sure. It doesn't sound much different from using two frames, one for the normal window, the other for a message box, then blocking input events for the former while the latter is still open.
Yeah, it is absolutely simillar, except that this will be done automatically instead of manually by the user, taking care of event blocking, parent fading and even dragging support. It will serve as the basis for a KW_CreateMessageBox()-of-sorts function.
KW_CreateModal() would fade the parent, create another frame on top. KW_CreateMessageBox() would use the return from KW_CreateModal() to setup editbox and buttons.
Hi, I've fixed the DestroyWidget bug in commit c663dea
Thanks, can confirm that this allows me to destroy widgets dynamically. Now I'm getting different kinds of mysterious failures instead in my wrapper code, will port it back to C again to see what that is about. It's not nearly as important to me as it appears that hiding widgets you don't need is the way to go.
edit: Perhaps the need for deleting widgets can be explained and solved in a different way. I've thought of a more elaborate demo where it would be very useful to dynamically create and clean up screens, a game menu where one enters and leaves menus as they please. While one could solve this in SDL2 directly, I could imagine an abstraction for this usecase to help, similar to IUP's dialogs. You define a hierarchical dialog first, then display and close it. There are prebuilt dialogs for message and input boxes available which are displayed as normal dialogs, but with a modal flag.
edit2: Fixed up my example now, no more mysterious failures :D
You define a hierarchical dialog first, then display and close it. There are prebuilt dialogs for message and input boxes available which are displayed as normal dialogs, but with a modal flag.
How would you picture this being done with KiWI? Can you describe a would-be API?
Not really, I've only used IUP from Scheme.
What if u made a drop down box??
That is a good idea and not hard to do, but I don't have the time right now to invest on it. Also, a request like this should be in another issue :)
oh ok sorry i’ll go put it in github ig sorry
On Tue, Jul 31, 2018 at 6:56 AM Leonardo Guilherme de Freitas < [email protected]> wrote:
That is a good idea and not hard to do, but I don't have the time right now to invest on it. Also, a request like this should be in another issue :)
— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/mobius3/KiWi/issues/15#issuecomment-409194120, or mute the thread https://github.com/notifications/unsubscribe-auth/AnkwpjK0Q7v6kl0Ed30OnieyvEQG9koPks5uMEXlgaJpZM4ImaXJ .