feat: add message box dialogs
This commit is contained in:
@@ -60,6 +60,7 @@ LDFLAGS := \
|
||||
--gc-sections \
|
||||
--hash-style=sysv \
|
||||
-u dialogs_run_request \
|
||||
-u dialogs_message_box \
|
||||
-m elf_x86_64 \
|
||||
-z max-page-size=0x1000
|
||||
|
||||
@@ -84,7 +85,7 @@ LIBC_CFLAGS := \
|
||||
-isystem $(PROG_INC)/libc \
|
||||
-isystem $(GCC_INCLUDE)
|
||||
|
||||
SRCS := main.cpp filedialog.cpp printdialog.cpp stb_truetype_impl.cpp font_data.cpp
|
||||
SRCS := main.cpp filedialog.cpp printdialog.cpp messagebox.cpp stb_truetype_impl.cpp font_data.cpp
|
||||
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
|
||||
LIBC_PIC_OBJ := $(OBJDIR)/libc_pic.o
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "main.hpp"
|
||||
#include "filedialog.hpp"
|
||||
#include "printdialog.hpp"
|
||||
#include "messagebox.hpp"
|
||||
|
||||
using namespace gui;
|
||||
namespace dlg = gui::dialogs;
|
||||
@@ -41,6 +42,8 @@ void dialog_cancel(const char* message) {
|
||||
void cleanup() {
|
||||
if (g_app.is_print) {
|
||||
printdialog::cleanup();
|
||||
} else if (g_app.is_message) {
|
||||
messagebox::cleanup();
|
||||
} else {
|
||||
filedialog::cleanup();
|
||||
}
|
||||
@@ -54,20 +57,26 @@ static void set_error_result(dlg::Result* result, const char* message) {
|
||||
dlg::safe_copy(result->message, sizeof(result->message), message);
|
||||
}
|
||||
|
||||
static bool ensure_fonts(dlg::Result* result) {
|
||||
if (g_fonts_ready) return true;
|
||||
if (!fonts::init()) {
|
||||
if (result)
|
||||
set_error_result(result, "failed to initialize dialog fonts");
|
||||
return false;
|
||||
}
|
||||
g_fonts_ready = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" bool dialogs_run_request(const dlg::Request* request, dlg::Result* out_result) {
|
||||
if (!request || !out_result) return false;
|
||||
|
||||
set_error_result(out_result, "dialog failed");
|
||||
|
||||
if (!g_fonts_ready) {
|
||||
if (!fonts::init()) {
|
||||
set_error_result(out_result, "failed to initialize dialog fonts");
|
||||
return true;
|
||||
}
|
||||
g_fonts_ready = true;
|
||||
}
|
||||
if (!ensure_fonts(out_result)) return true;
|
||||
|
||||
if (request->kind != dlg::REQUEST_KIND_FILE && request->kind != dlg::REQUEST_KIND_PRINT) {
|
||||
if (request->kind != dlg::REQUEST_KIND_FILE &&
|
||||
request->kind != dlg::REQUEST_KIND_PRINT) {
|
||||
set_error_result(out_result, "unsupported dialog request");
|
||||
return true;
|
||||
}
|
||||
@@ -78,9 +87,16 @@ extern "C" bool dialogs_run_request(const dlg::Request* request, dlg::Result* ou
|
||||
dlg::reset_result(&g_app.result);
|
||||
|
||||
g_app.is_print = g_app.request.kind == dlg::REQUEST_KIND_PRINT;
|
||||
const int init_w = g_app.is_print ? printdialog::PRINT_INIT_W : filedialog::FILE_INIT_W;
|
||||
const int init_h = g_app.is_print ? printdialog::PRINT_INIT_H : filedialog::FILE_INIT_H;
|
||||
const char* title = g_app.request.title[0] ? g_app.request.title : (g_app.is_print ? "Print" : "Open");
|
||||
|
||||
int init_w = filedialog::FILE_INIT_W;
|
||||
int init_h = filedialog::FILE_INIT_H;
|
||||
if (g_app.is_print) {
|
||||
init_w = printdialog::PRINT_INIT_W;
|
||||
init_h = printdialog::PRINT_INIT_H;
|
||||
}
|
||||
|
||||
const char* default_title = g_app.is_print ? "Print" : "Open";
|
||||
const char* title = g_app.request.title[0] ? g_app.request.title : default_title;
|
||||
|
||||
if (g_app.is_print) printdialog::init(g_app.request);
|
||||
else filedialog::init(g_app.request);
|
||||
@@ -136,3 +152,60 @@ extern "C" bool dialogs_run_request(const dlg::Request* request, dlg::Result* ou
|
||||
*out_result = g_app.result;
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" uint8_t dialogs_message_box(const char* title,
|
||||
const char* message,
|
||||
uint8_t buttons) {
|
||||
if (!ensure_fonts(nullptr))
|
||||
return dlg::MESSAGE_BOX_RESULT_NONE;
|
||||
|
||||
g_app = AppState {};
|
||||
g_app.running = true;
|
||||
g_app.is_message = true;
|
||||
|
||||
const char* window_title = title && title[0] ? title : "Message";
|
||||
messagebox::init(message ? message : "", buttons);
|
||||
|
||||
if (!g_app.win.create(window_title, messagebox::MESSAGE_INIT_W, messagebox::MESSAGE_INIT_H)) {
|
||||
cleanup();
|
||||
return dlg::MESSAGE_BOX_RESULT_NONE;
|
||||
}
|
||||
|
||||
messagebox::draw();
|
||||
g_app.win.present();
|
||||
|
||||
while (g_app.running) {
|
||||
messagebox::draw();
|
||||
g_app.win.present();
|
||||
|
||||
Montauk::WinEvent ev;
|
||||
int rc = g_app.win.poll(&ev);
|
||||
if (rc < 0) {
|
||||
messagebox::dismiss();
|
||||
break;
|
||||
}
|
||||
if (rc == 0) {
|
||||
montauk::sleep_ms(16);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ev.type == 3) {
|
||||
messagebox::dismiss();
|
||||
break;
|
||||
}
|
||||
if (ev.type == 2 || ev.type == 4) {
|
||||
continue;
|
||||
}
|
||||
if (ev.type == 1) {
|
||||
messagebox::handle_mouse(ev);
|
||||
continue;
|
||||
}
|
||||
if (ev.type == 0) {
|
||||
messagebox::handle_key(ev.key);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t choice = messagebox::selected_choice();
|
||||
cleanup();
|
||||
return choice ? choice : (uint8_t)dlg::MESSAGE_BOX_RESULT_NONE;
|
||||
}
|
||||
|
||||
@@ -16,10 +16,11 @@ struct AppState {
|
||||
gui::dialogs::Result result;
|
||||
bool running;
|
||||
bool is_print;
|
||||
bool is_message;
|
||||
};
|
||||
|
||||
extern AppState g_app;
|
||||
|
||||
void dialog_finish(const char* status, const char* path, const char* job_id, const char* message,
|
||||
const char* printer_uri = "", const char* printer_name = "", uint32_t copies = 0);
|
||||
void dialog_cancel(const char* message = "");
|
||||
void dialog_cancel(const char* message = "");
|
||||
|
||||
@@ -0,0 +1,416 @@
|
||||
/*
|
||||
* messagebox.cpp
|
||||
* Message box dialog implementation
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "messagebox.hpp"
|
||||
#include "main.hpp"
|
||||
#include <montauk/string.h>
|
||||
#include <gui/canvas.hpp>
|
||||
#include <gui/font.hpp>
|
||||
#include <gui/mtk/widgets.hpp>
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace gui;
|
||||
|
||||
struct MessageButton {
|
||||
const char* label;
|
||||
uint8_t choice;
|
||||
bool primary;
|
||||
};
|
||||
|
||||
struct MessageBoxState {
|
||||
char text[512];
|
||||
uint8_t buttons;
|
||||
uint8_t default_choice;
|
||||
int mouse_x;
|
||||
int mouse_y;
|
||||
int wrapped_width;
|
||||
int line_count;
|
||||
bool overflow;
|
||||
char lines[messagebox::MAX_LINES][messagebox::MAX_LINE_LEN];
|
||||
MessageButton button_specs[messagebox::MAX_BUTTONS];
|
||||
int button_count;
|
||||
uint8_t choice;
|
||||
};
|
||||
|
||||
MessageBoxState g_message;
|
||||
|
||||
uint8_t sanitize_buttons(uint8_t buttons) {
|
||||
switch (buttons) {
|
||||
case gui::dialogs::MESSAGE_BOX_OK:
|
||||
case gui::dialogs::MESSAGE_BOX_OK_CANCEL:
|
||||
case gui::dialogs::MESSAGE_BOX_YES_NO:
|
||||
case gui::dialogs::MESSAGE_BOX_YES_NO_CANCEL:
|
||||
return buttons;
|
||||
default:
|
||||
return gui::dialogs::MESSAGE_BOX_OK;
|
||||
}
|
||||
}
|
||||
|
||||
void add_button(MessageBoxState* st, const char* label, uint8_t choice, bool primary) {
|
||||
if (!st || st->button_count >= messagebox::MAX_BUTTONS) return;
|
||||
st->button_specs[st->button_count++] = {label, choice, primary};
|
||||
}
|
||||
|
||||
bool has_choice(const MessageBoxState* st, uint8_t choice) {
|
||||
if (!st) return false;
|
||||
for (int i = 0; i < st->button_count; i++) {
|
||||
if (st->button_specs[i].choice == choice) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t default_choice_for(uint8_t buttons) {
|
||||
if (buttons == gui::dialogs::MESSAGE_BOX_YES_NO ||
|
||||
buttons == gui::dialogs::MESSAGE_BOX_YES_NO_CANCEL) {
|
||||
return gui::dialogs::MESSAGE_BOX_RESULT_YES;
|
||||
}
|
||||
return gui::dialogs::MESSAGE_BOX_RESULT_OK;
|
||||
}
|
||||
|
||||
uint8_t dismiss_choice_for(uint8_t buttons) {
|
||||
switch (buttons) {
|
||||
case gui::dialogs::MESSAGE_BOX_OK:
|
||||
return gui::dialogs::MESSAGE_BOX_RESULT_OK;
|
||||
case gui::dialogs::MESSAGE_BOX_OK_CANCEL:
|
||||
case gui::dialogs::MESSAGE_BOX_YES_NO_CANCEL:
|
||||
return gui::dialogs::MESSAGE_BOX_RESULT_CANCEL;
|
||||
case gui::dialogs::MESSAGE_BOX_YES_NO:
|
||||
return gui::dialogs::MESSAGE_BOX_RESULT_NO;
|
||||
default:
|
||||
return gui::dialogs::MESSAGE_BOX_RESULT_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
void configure_buttons(MessageBoxState* st) {
|
||||
if (!st) return;
|
||||
st->button_count = 0;
|
||||
switch (st->buttons) {
|
||||
case gui::dialogs::MESSAGE_BOX_OK_CANCEL:
|
||||
add_button(st, "OK", gui::dialogs::MESSAGE_BOX_RESULT_OK, true);
|
||||
add_button(st, "Cancel", gui::dialogs::MESSAGE_BOX_RESULT_CANCEL, false);
|
||||
break;
|
||||
case gui::dialogs::MESSAGE_BOX_YES_NO:
|
||||
add_button(st, "Yes", gui::dialogs::MESSAGE_BOX_RESULT_YES, true);
|
||||
add_button(st, "No", gui::dialogs::MESSAGE_BOX_RESULT_NO, false);
|
||||
break;
|
||||
case gui::dialogs::MESSAGE_BOX_YES_NO_CANCEL:
|
||||
add_button(st, "Yes", gui::dialogs::MESSAGE_BOX_RESULT_YES, true);
|
||||
add_button(st, "No", gui::dialogs::MESSAGE_BOX_RESULT_NO, false);
|
||||
add_button(st, "Cancel", gui::dialogs::MESSAGE_BOX_RESULT_CANCEL, false);
|
||||
break;
|
||||
case gui::dialogs::MESSAGE_BOX_OK:
|
||||
default:
|
||||
add_button(st, "OK", gui::dialogs::MESSAGE_BOX_RESULT_OK, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void safe_append(char* dst, int dst_len, const char* src) {
|
||||
if (!dst || dst_len <= 0 || !src) return;
|
||||
int len = montauk::slen(dst);
|
||||
int i = 0;
|
||||
while (src[i] && len < dst_len - 1)
|
||||
dst[len++] = src[i++];
|
||||
dst[len] = '\0';
|
||||
}
|
||||
|
||||
void fit_text_end(const char* src, char* out, int out_len, int max_px) {
|
||||
if (!out || out_len <= 0) return;
|
||||
if (!src || !src[0]) {
|
||||
out[0] = '\0';
|
||||
return;
|
||||
}
|
||||
if (text_width(src) <= max_px) {
|
||||
gui::dialogs::safe_copy(out, out_len, src);
|
||||
return;
|
||||
}
|
||||
|
||||
int slen = montauk::slen(src);
|
||||
int keep = slen;
|
||||
while (keep > 1) {
|
||||
char candidate[messagebox::MAX_LINE_LEN];
|
||||
candidate[0] = '.';
|
||||
candidate[1] = '.';
|
||||
candidate[2] = '.';
|
||||
candidate[3] = '\0';
|
||||
const char* tail = src + (slen - keep);
|
||||
safe_append(candidate, sizeof(candidate), tail);
|
||||
if (text_width(candidate) <= max_px) {
|
||||
gui::dialogs::safe_copy(out, out_len, candidate);
|
||||
return;
|
||||
}
|
||||
keep--;
|
||||
}
|
||||
|
||||
gui::dialogs::safe_copy(out, out_len, "...");
|
||||
}
|
||||
|
||||
void mark_overflow(MessageBoxState* st) {
|
||||
if (!st) return;
|
||||
st->overflow = true;
|
||||
if (messagebox::MAX_LINES <= 0) return;
|
||||
if (st->line_count <= 0) st->line_count = 1;
|
||||
if (st->line_count > messagebox::MAX_LINES) st->line_count = messagebox::MAX_LINES;
|
||||
gui::dialogs::safe_copy(st->lines[st->line_count - 1], messagebox::MAX_LINE_LEN, "...");
|
||||
}
|
||||
|
||||
void push_line(MessageBoxState* st, const char* line) {
|
||||
if (!st || st->overflow) return;
|
||||
if (st->line_count >= messagebox::MAX_LINES) {
|
||||
mark_overflow(st);
|
||||
return;
|
||||
}
|
||||
gui::dialogs::safe_copy(st->lines[st->line_count], messagebox::MAX_LINE_LEN, line ? line : "");
|
||||
st->line_count++;
|
||||
}
|
||||
|
||||
void commit_word(MessageBoxState* st, char* current, int* current_len, const char* word, int max_px) {
|
||||
if (!st || st->overflow || !current || !current_len || !word || !word[0]) return;
|
||||
|
||||
char candidate[messagebox::MAX_LINE_LEN];
|
||||
gui::dialogs::safe_copy(candidate, sizeof(candidate), current);
|
||||
if (*current_len > 0) safe_append(candidate, sizeof(candidate), " ");
|
||||
safe_append(candidate, sizeof(candidate), word);
|
||||
|
||||
if (*current_len == 0 && text_width(word) > max_px) {
|
||||
char fitted[messagebox::MAX_LINE_LEN];
|
||||
fit_text_end(word, fitted, sizeof(fitted), max_px);
|
||||
push_line(st, fitted);
|
||||
current[0] = '\0';
|
||||
*current_len = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (*current_len == 0 || text_width(candidate) <= max_px) {
|
||||
gui::dialogs::safe_copy(current, messagebox::MAX_LINE_LEN, candidate);
|
||||
*current_len = montauk::slen(current);
|
||||
return;
|
||||
}
|
||||
|
||||
push_line(st, current);
|
||||
if (st->overflow) return;
|
||||
|
||||
current[0] = '\0';
|
||||
*current_len = 0;
|
||||
if (text_width(word) > max_px) {
|
||||
char fitted[messagebox::MAX_LINE_LEN];
|
||||
fit_text_end(word, fitted, sizeof(fitted), max_px);
|
||||
push_line(st, fitted);
|
||||
} else {
|
||||
gui::dialogs::safe_copy(current, messagebox::MAX_LINE_LEN, word);
|
||||
*current_len = montauk::slen(current);
|
||||
}
|
||||
}
|
||||
|
||||
bool is_space(char ch) {
|
||||
return ch == ' ' || ch == '\t' || ch == '\r';
|
||||
}
|
||||
|
||||
void wrap_message(MessageBoxState* st, int max_px) {
|
||||
if (!st) return;
|
||||
if (max_px < 40) max_px = 40;
|
||||
|
||||
st->line_count = 0;
|
||||
st->overflow = false;
|
||||
|
||||
char current[messagebox::MAX_LINE_LEN] = {};
|
||||
int current_len = 0;
|
||||
char word[messagebox::MAX_LINE_LEN] = {};
|
||||
int word_len = 0;
|
||||
|
||||
for (const char* p = st->text; ; p++) {
|
||||
char ch = *p;
|
||||
bool at_end = ch == '\0';
|
||||
bool separator = at_end || ch == '\n' || is_space(ch);
|
||||
|
||||
if (separator) {
|
||||
if (word_len > 0) {
|
||||
word[word_len] = '\0';
|
||||
commit_word(st, current, ¤t_len, word, max_px);
|
||||
word_len = 0;
|
||||
word[0] = '\0';
|
||||
}
|
||||
|
||||
if (ch == '\n') {
|
||||
push_line(st, current);
|
||||
current[0] = '\0';
|
||||
current_len = 0;
|
||||
}
|
||||
|
||||
if (at_end) break;
|
||||
if (st->overflow) break;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (word_len >= messagebox::MAX_LINE_LEN - 1) {
|
||||
word[word_len] = '\0';
|
||||
commit_word(st, current, ¤t_len, word, max_px);
|
||||
word_len = 0;
|
||||
word[0] = '\0';
|
||||
}
|
||||
if (word_len < messagebox::MAX_LINE_LEN - 1)
|
||||
word[word_len++] = ch;
|
||||
}
|
||||
|
||||
if (!st->overflow && (current_len > 0 || st->line_count == 0))
|
||||
push_line(st, current);
|
||||
|
||||
st->wrapped_width = max_px;
|
||||
}
|
||||
|
||||
MessageBoxLayout message_layout(const MessageBoxState* st) {
|
||||
MessageBoxLayout lo = {};
|
||||
lo.button_count = st ? st->button_count : 0;
|
||||
lo.footer_rect = {0, g_app.win.height - messagebox::FOOTER_H, g_app.win.width, messagebox::FOOTER_H};
|
||||
|
||||
lo.text_rect = {20, 24, g_app.win.width - 40, lo.footer_rect.y - 32};
|
||||
|
||||
int total_w = lo.button_count * messagebox::BUTTON_W;
|
||||
if (lo.button_count > 1) total_w += (lo.button_count - 1) * messagebox::BUTTON_GAP;
|
||||
int x = g_app.win.width - 16 - total_w;
|
||||
int y = lo.footer_rect.y + (messagebox::FOOTER_H - messagebox::BUTTON_H) / 2;
|
||||
for (int i = 0; i < lo.button_count && i < messagebox::MAX_BUTTONS; i++) {
|
||||
lo.buttons[i] = {x, y, messagebox::BUTTON_W, messagebox::BUTTON_H};
|
||||
x += messagebox::BUTTON_W + messagebox::BUTTON_GAP;
|
||||
}
|
||||
|
||||
return lo;
|
||||
}
|
||||
|
||||
mtk::Theme message_theme() {
|
||||
mtk::Theme theme = mtk::make_theme();
|
||||
theme.surface = Color::from_rgb(0xF7, 0xF7, 0xF7);
|
||||
theme.surface_hover = mtk::mix(theme.surface, theme.accent, 18);
|
||||
return theme;
|
||||
}
|
||||
|
||||
void finish(uint8_t choice) {
|
||||
g_message.choice = choice;
|
||||
g_app.running = false;
|
||||
}
|
||||
|
||||
void draw_message_box() {
|
||||
MessageBoxState* st = &g_message;
|
||||
Canvas c = g_app.win.canvas();
|
||||
c.fill(colors::WINDOW_BG);
|
||||
|
||||
MessageBoxLayout lo = message_layout(st);
|
||||
if (st->wrapped_width != lo.text_rect.w)
|
||||
wrap_message(st, lo.text_rect.w);
|
||||
|
||||
int line_h = system_font_height() + 5;
|
||||
int y = lo.text_rect.y;
|
||||
for (int i = 0; i < st->line_count; i++) {
|
||||
if (y + line_h > lo.footer_rect.y - 8) break;
|
||||
c.text(lo.text_rect.x, y, st->lines[i], colors::TEXT_COLOR);
|
||||
y += line_h;
|
||||
}
|
||||
|
||||
c.fill_rect(lo.footer_rect.x, lo.footer_rect.y, lo.footer_rect.w, lo.footer_rect.h,
|
||||
Color::from_rgb(0xF7, 0xF7, 0xF7));
|
||||
c.hline(0, lo.footer_rect.y, c.w, colors::BORDER);
|
||||
|
||||
mtk::Theme theme = message_theme();
|
||||
for (int i = 0; i < st->button_count && i < messagebox::MAX_BUTTONS; i++) {
|
||||
bool hovered = lo.buttons[i].contains(st->mouse_x, st->mouse_y);
|
||||
mtk::WidgetState state = mtk::widget_state(false, hovered, true);
|
||||
mtk::draw_button(c, lo.buttons[i], st->button_specs[i].label,
|
||||
st->button_specs[i].primary ? mtk::BUTTON_PRIMARY : mtk::BUTTON_SECONDARY,
|
||||
state, theme);
|
||||
}
|
||||
}
|
||||
|
||||
void handle_message_mouse(const Montauk::WinEvent& ev) {
|
||||
MessageBoxState* st = &g_message;
|
||||
st->mouse_x = ev.mouse.x;
|
||||
st->mouse_y = ev.mouse.y;
|
||||
|
||||
bool left_pressed = (ev.mouse.buttons & 0x01) && !(ev.mouse.prev_buttons & 0x01);
|
||||
if (!left_pressed) return;
|
||||
|
||||
MessageBoxLayout lo = message_layout(st);
|
||||
for (int i = 0; i < st->button_count && i < messagebox::MAX_BUTTONS; i++) {
|
||||
if (lo.buttons[i].contains(ev.mouse.x, ev.mouse.y)) {
|
||||
finish(st->button_specs[i].choice);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handle_message_key(const Montauk::KeyEvent& key) {
|
||||
if (!key.pressed) return;
|
||||
|
||||
MessageBoxState* st = &g_message;
|
||||
if (key.scancode == 0x01) {
|
||||
finish(dismiss_choice_for(st->buttons));
|
||||
return;
|
||||
}
|
||||
if (key.ascii == '\n' || key.ascii == '\r') {
|
||||
finish(st->default_choice);
|
||||
return;
|
||||
}
|
||||
if ((key.ascii == 'o' || key.ascii == 'O') &&
|
||||
has_choice(st, gui::dialogs::MESSAGE_BOX_RESULT_OK)) {
|
||||
finish(gui::dialogs::MESSAGE_BOX_RESULT_OK);
|
||||
return;
|
||||
}
|
||||
if ((key.ascii == 'c' || key.ascii == 'C') &&
|
||||
has_choice(st, gui::dialogs::MESSAGE_BOX_RESULT_CANCEL)) {
|
||||
finish(gui::dialogs::MESSAGE_BOX_RESULT_CANCEL);
|
||||
return;
|
||||
}
|
||||
if ((key.ascii == 'y' || key.ascii == 'Y') &&
|
||||
has_choice(st, gui::dialogs::MESSAGE_BOX_RESULT_YES)) {
|
||||
finish(gui::dialogs::MESSAGE_BOX_RESULT_YES);
|
||||
return;
|
||||
}
|
||||
if ((key.ascii == 'n' || key.ascii == 'N') &&
|
||||
has_choice(st, gui::dialogs::MESSAGE_BOX_RESULT_NO)) {
|
||||
finish(gui::dialogs::MESSAGE_BOX_RESULT_NO);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace messagebox {
|
||||
|
||||
void init(const char* message, uint8_t buttons, uint8_t default_choice) {
|
||||
MessageBoxState* st = &g_message;
|
||||
montauk::memset(st, 0, sizeof(*st));
|
||||
gui::dialogs::safe_copy(st->text, sizeof(st->text), message ? message : "");
|
||||
st->buttons = sanitize_buttons(buttons);
|
||||
st->default_choice = default_choice ? default_choice : default_choice_for(st->buttons);
|
||||
configure_buttons(st);
|
||||
if (!has_choice(st, st->default_choice))
|
||||
st->default_choice = default_choice_for(st->buttons);
|
||||
wrap_message(st, MESSAGE_INIT_W - 40);
|
||||
}
|
||||
|
||||
void draw() {
|
||||
draw_message_box();
|
||||
}
|
||||
|
||||
void handle_mouse(const Montauk::WinEvent& ev) {
|
||||
handle_message_mouse(ev);
|
||||
}
|
||||
|
||||
void handle_key(const Montauk::KeyEvent& key) {
|
||||
handle_message_key(key);
|
||||
}
|
||||
|
||||
void dismiss() {
|
||||
finish(dismiss_choice_for(g_message.buttons));
|
||||
}
|
||||
|
||||
uint8_t selected_choice() {
|
||||
return g_message.choice;
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
}
|
||||
|
||||
} // namespace messagebox
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* messagebox.hpp
|
||||
* Message box dialog declarations
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <montauk/syscall.h>
|
||||
#include <gui/gui.hpp>
|
||||
#include <gui/dialogs.hpp>
|
||||
|
||||
struct MessageBoxLayout {
|
||||
gui::Rect text_rect;
|
||||
gui::Rect footer_rect;
|
||||
gui::Rect buttons[3];
|
||||
int button_count;
|
||||
};
|
||||
|
||||
namespace messagebox {
|
||||
|
||||
constexpr int MESSAGE_INIT_W = 460;
|
||||
constexpr int MESSAGE_INIT_H = 220;
|
||||
constexpr int FOOTER_H = 56;
|
||||
constexpr int BUTTON_W = 88;
|
||||
constexpr int BUTTON_H = 30;
|
||||
constexpr int BUTTON_GAP = 8;
|
||||
constexpr int MAX_BUTTONS = 3;
|
||||
constexpr int MAX_LINES = 7;
|
||||
constexpr int MAX_LINE_LEN = 128;
|
||||
|
||||
void init(const char* message, uint8_t buttons, uint8_t default_choice = 0);
|
||||
void draw();
|
||||
void handle_mouse(const Montauk::WinEvent& ev);
|
||||
void handle_key(const Montauk::KeyEvent& key);
|
||||
void dismiss();
|
||||
uint8_t selected_choice();
|
||||
void cleanup();
|
||||
|
||||
} // namespace messagebox
|
||||
Reference in New Issue
Block a user