mobius3 / kiwi

KiWi: Killer Widgets
zlib License
186 stars 19 forks source link

Implementing a message box #15

Open wasamasa opened 8 years ago

wasamasa commented 8 years ago

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:

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 commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

Ah. I'll look into these problems.

mobius3 commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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

wasamasa commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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

JU5TABU5T commented 6 years ago

What if u made a drop down box??

mobius3 commented 6 years ago

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 commented 6 years ago

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 < notifications@github.com> 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 .