rsgt 0.2.1

Rust simple GUI Toolkit
//========================================================================
// RSGT | msgbox.rs - https://overtimecoder.github.io
//------------------------------------------------------------------------
// Message Box
//------------------------------------------------------------------------
//
// Author LatteS
//
// File was created in 2022/12/31
//
//========================================================================

//! # Message Box

use crate::os::enable_visual_style;
use crate::ResponseType;

// Windows constants

/// A type that specifies the style of the message box
#[cfg(windows)]
pub type MsgBoxStyle = windows_sys::Win32::UI::WindowsAndMessaging::MESSAGEBOX_STYLE;
#[cfg(unix)]
pub type MsgBoxStyle = gtk::MessageType;
#[cfg(windows)]
pub type MsgBoxButtonStyle = windows_sys::Win32::UI::WindowsAndMessaging::MESSAGEBOX_STYLE;
#[cfg(unix)]
pub type MsgBoxButtonStyle = gtk::ButtonsType;

// Button styles

#[cfg(windows)]
pub const BS_NONE: MsgBoxButtonStyle = 0;
#[cfg(unix)]
pub const BS_NONE: MsgBoxButtonStyle = gtk::ButtonsType::None;

/// Set the button in the message box to OK
#[cfg(windows)]
pub const BS_OK: MsgBoxButtonStyle = windows_sys::Win32::UI::WindowsAndMessaging::MB_OK;
#[cfg(unix)]
pub const BS_OK: MsgBoxButtonStyle = gtk::ButtonsType::Ok;
/// Set the button in the message box to Yes and No
#[cfg(windows)]
pub const BS_YESNO: MsgBoxButtonStyle = windows_sys::Win32::UI::WindowsAndMessaging::MB_YESNO;
#[cfg(unix)]
pub const BS_YESNO: MsgBoxButtonStyle = gtk::ButtonsType::YesNo;
/// Set the button in the message box to OK and Cancel
#[cfg(windows)]
pub const BS_OKCANCEL: MsgBoxButtonStyle = windows_sys::Win32::UI::WindowsAndMessaging::MB_OKCANCEL;
#[cfg(unix)]
pub const BS_OKCANCEL: MsgBoxButtonStyle = gtk::ButtonsType::OkCancel;

// Message box type
#[cfg(windows)]
pub const MS_NONE: MsgBoxStyle = 0;
#[cfg(unix)]
pub const MS_NONE: MsgBoxStyle = gtk::MessageType::Other;
/// Change the message box to an information dialog
#[cfg(windows)]
pub const MS_INFORMATION: MsgBoxStyle =
    windows_sys::Win32::UI::WindowsAndMessaging::MB_ICONINFORMATION;
#[cfg(unix)]
pub const MS_INFORMATION: MsgBoxStyle = gtk::MessageType::Info;
/// Change the message box to an question dialog
#[cfg(windows)]
pub const MS_QUESTION: MsgBoxStyle = windows_sys::Win32::UI::WindowsAndMessaging::MB_ICONQUESTION;
#[cfg(unix)]
pub const MS_QUESTION: MsgBoxStyle = gtk::MessageType::Question;
/// Change the message box to an warning dialog
#[cfg(windows)]
pub const MS_WARNING: MsgBoxStyle = windows_sys::Win32::UI::WindowsAndMessaging::MB_ICONWARNING;
#[cfg(unix)]
pub const MS_WARNING: MsgBoxStyle = gtk::MessageType::Warning;
/// Change the message box to an error dialog
#[cfg(windows)]
pub const MS_ERROR: MsgBoxStyle = windows_sys::Win32::UI::WindowsAndMessaging::MB_ICONERROR;
#[cfg(unix)]
pub const MS_ERROR: MsgBoxStyle = gtk::MessageType::Error;

#[cfg(windows)]
type Result = windows_sys::Win32::UI::WindowsAndMessaging::MESSAGEBOX_RESULT;
#[cfg(unix)]
type Result = gtk::ResponseType;

pub struct MsgBox {
    text: String,
    caption: String,
    style: MsgBoxStyle,
    button_style: MsgBoxButtonStyle,
}

impl MsgBox {
    // New method
    /// Initialize the message box.
    pub fn new(
        text: impl Into<String>,
        caption: impl Into<String>,
        style: MsgBoxStyle,
        button_style: MsgBoxButtonStyle,
    ) -> Self {
        Self {
            text: text.into(),
            caption: caption.into(),
            style,
            button_style,
        }
    }

    /// Displays a message box.
    pub fn run(&self) -> ResponseType {
        enable_visual_style();
        let result =
            MsgBox::show_base_dialog(&*self.text, &*self.caption, self.style, self.button_style);
        MsgBox::match_response_type(result)
    }

    // Related Functions
    /// Base method for displaying a message box
    #[cfg(windows)]
    fn show_base_dialog(
        text: &str,
        caption: &str,
        style: MsgBoxStyle,
        button_style: MsgBoxButtonStyle,
    ) -> Result {
        use std::iter::once;
        use windows_sys::{
            Win32::Foundation::*, Win32::System::Threading::*, Win32::UI::WindowsAndMessaging::*,
        };
        unsafe {
            let event = CreateEventW(std::ptr::null(), 1, 0, std::ptr::null());
            SetEvent(event);
            WaitForSingleObject(event, 0);
            CloseHandle(event);

            MessageBoxW(
                0,
                text.encode_utf16()
                    .chain(once(0))
                    .collect::<Vec<u16>>()
                    .as_mut_ptr(),
                caption
                    .encode_utf16()
                    .chain(once(0))
                    .collect::<Vec<u16>>()
                    .as_mut_ptr(),
                style | button_style,
            )
        }
    }

    #[cfg(unix)]
    fn show_base_dialog(
        text: &str,
        caption: &str,
        style: MsgBoxStyle,
        button_style: MsgBoxButtonStyle,
    ) -> Result {
        use gtk::prelude::*;
        gtk::init().expect("Failed to initialize gtk.");
        let window = gtk::Window::new(gtk::WindowType::Toplevel);
        window.set_title(caption);

        let dialog = gtk::MessageDialog::new(
            Some(&window),
            gtk::DialogFlags::empty(),
            style,
            button_style,
            text,
        );
        let result = dialog.run();
        unsafe {
            dialog.destroy();
        }
        result
    }

    #[cfg(windows)]
    fn match_response_type(result: Result) -> ResponseType {
        use windows_sys::Win32::UI::WindowsAndMessaging::*;
        match result {
            IDOK => ResponseType::Ok,
            IDYES => ResponseType::Yes,
            IDNO => ResponseType::No,
            IDCANCEL => ResponseType::Cancel,
            IDCLOSE => ResponseType::Close,
            _ => ResponseType::None,
        }
    }

    #[cfg(unix)]
    fn match_response_type(result: Result) -> ResponseType {
        match result {
            gtk::ResponseType::Ok => ResponseType::Ok,
            gtk::ResponseType::Yes => ResponseType::Yes,
            gtk::ResponseType::No => ResponseType::No,
            gtk::ResponseType::Cancel => ResponseType::Cancel,
            gtk::ResponseType::Close => ResponseType::Close,
            _ => ResponseType::None,
        }
    }

    pub fn show_dialog(text: &str, caption: &str) -> ResponseType {
        let result = MsgBox::show_base_dialog(text, caption, MS_NONE, BS_OK);
        MsgBox::match_response_type(result)
    }

    pub fn show_information_dialog(text: &str, caption: &str) -> ResponseType {
        let result = MsgBox::show_base_dialog(text, caption, MS_INFORMATION, BS_OK);
        MsgBox::match_response_type(result)
    }

    pub fn show_question_dialog(text: &str, caption: &str) -> ResponseType {
        let result = MsgBox::show_base_dialog(text, caption, MS_QUESTION, BS_YESNO);
        MsgBox::match_response_type(result)
    }

    pub fn show_warning_dialog(text: &str, caption: &str) -> ResponseType {
        let result = MsgBox::show_base_dialog(text, caption, MS_WARNING, BS_OK);
        MsgBox::match_response_type(result)
    }

    pub fn show_error_dialog(text: &str, caption: &str) -> ResponseType {
        let result = MsgBox::show_base_dialog(text, caption, MS_ERROR, BS_OK);
        MsgBox::match_response_type(result)
    }
}