Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing a message box #15

Open
wasamasa opened this issue May 25, 2016 · 20 comments
Open

Implementing a message box #15

wasamasa opened this issue May 25, 2016 · 20 comments
Assignees

Comments

@wasamasa
Copy link
Contributor

wasamasa commented May 25, 2016

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).

@mobius3
Copy link
Owner

mobius3 commented May 25, 2016

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.

@wasamasa
Copy link
Contributor Author

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).

@wasamasa
Copy link
Contributor Author

wasamasa commented May 25, 2016

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)

@mobius3
Copy link
Owner

mobius3 commented May 25, 2016

Ah. I'll look into these problems.

@mobius3
Copy link
Owner

mobius3 commented May 25, 2016

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.

@wasamasa
Copy link
Contributor Author

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;
}

@mobius3
Copy link
Owner

mobius3 commented May 26, 2016

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.

tileset

Nice work, by the way.

@wasamasa
Copy link
Contributor Author

wasamasa commented May 27, 2016

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?

@mobius3
Copy link
Owner

mobius3 commented May 27, 2016

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;
}

@wasamasa
Copy link
Contributor Author

That does indeed look like a solution, but won't do as soon as there's some convenience function for opening a message box.

@mobius3
Copy link
Owner

mobius3 commented May 28, 2016

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

@wasamasa
Copy link
Contributor Author

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.

@mobius3
Copy link
Owner

mobius3 commented May 30, 2016

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.

@mobius3
Copy link
Owner

mobius3 commented May 30, 2016

Hi, I've fixed the DestroyWidget bug in commit c663dea

@wasamasa
Copy link
Contributor Author

wasamasa commented May 31, 2016

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

@mobius3
Copy link
Owner

mobius3 commented May 31, 2016

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?

@wasamasa
Copy link
Contributor Author

Not really, I've only used IUP from Scheme.

@JU5TABU5T
Copy link

What if u made a drop down box??

@mobius3
Copy link
Owner

mobius3 commented Jul 31, 2018

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 :)

@JU5TABU5T
Copy link

JU5TABU5T commented Jul 31, 2018 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants