gammasoft71 / xtd

Free open-source modern C++17 / C++20 framework to create console, GUI (forms like WinForms) and unit test applications and libraries on Microsoft Windows, Apple macOS and Linux.
https://gammasoft71.github.io/xtd
MIT License
737 stars 58 forks source link

[ENHANCEMENT] xtd.forms - message_notifier #180

Open gammasoft71 opened 2 years ago

gammasoft71 commented 2 years ago

xtd.forms - xtd::forms::message_notifier

Library

xtd.forms

Enhancement

xtd::forms::message_notifier

Description

message_notifier is a small, nonblocking notification pop-up. A message_notifier is shown to users with readable message content at the bottom or top of the screen or at a specific target and disappears automatically after a few seconds (time-out). The control has various built-in options for customizing visual elements, durations, and dismissing toasts.

First draft

Unfortunately, the different OS don't manage the notifications in the same way. So, we should have as for the dialog box xtd::forms::about_dialog manage the two styles of dialog:

Notification system :

Use wxNotificationMessage whenever possible. But typically, on macOS it doesn't work (Maybe for a user right ?). And if the wxWidgets component doesn't match maybe call the native version for each OS (as I did for xtd::forms::message_box which was not implemented correctly in macOS and Windows).

Standard notification :

Implement our own xtd::forms::form (no border, no title and no controls). Add an image, a message and the possibility of 1, 2 or more buttons. Add an xtd::forms::timer for automatic closing.

Common

API

Globally the API will be close to xtd::forms::message_dialog and xtd::forms::message_box with the possibility to have custom buttons and a timer to close the notification automatically.

baderouaich commented 2 years ago

It may be a good approach to be able to use xtd::forms::message_notifier like this (as initial mock-up):

message_notifier mn;
mn.title("TITLE");
mn.message("MESSAGE"):
mn.notifier_style(message_notifier_style::information | warning | error); // if an icon is set, the system icon style will be overwritten with the icon.
mn.icon(xtd::drawing::icon::empty); // if icon is empty, system icon style (message_notifier_style) will be displayed instead.
mn.button_action("Ok", []() {
    xtd::diagnostics::debug::write_line("Action: Ok");
 });
mn.button_action("Cancel", []() {
    xtd::diagnostics::debug::write_line("Action: Cancel");
 });
 // .. as many button actions as we like ..
mn.click += [] {
    xtd::diagnostics::debug::write_line("Event: message notifier clicked");
};
mn.notifier_closed += [] {
    xtd::diagnostics::debug::write_line("Event: message notifier closed");
};
mn.timeout(10 * 1000); // timeout in milliseconds
mn.show();
gammasoft71 commented 2 years ago

For the events, I prefer to have only a close event and to be able to get the result of the notifier a bit like for the message_dialog. This allows to keep the same consistency in the whole framework. If someone knows the message_dialog, he automatically knows the message_notifier and vice versa ;-).

I really see the message_notifier class as the message_dialog class. With a timeout that can be activated or not to close automatically the notification. And just the possibility to change the text of the buttons (I would also like to add this possibility to message_dialog ;-)).

Look at the following example of the message_dialog in asynchronous.

Implementation sheet...

namespace xtd {
  enum class notifier_result {
    ok,
    cancel,
    yes,
    no,
    //...
  };

  class notifier_closed_event_args : public xtd::event_Args {
  public:
    //...

    xtd::forms::notifier_result notifier_reult() const;

  //...
  };

  class message_notifier : public xtd::forms::componant {
  public:
    /// ...

    /// Properties

    bool close_timeout_enabled() const;
    message_notifier& close_timeout_enabled(bool value);

    std::chrono::milliseconds close_timeout_interval() const;
    message_notifier& close_timeout_interval(std::chrono::milliseconds value);
    message_notifier& close_timeout_interval_milliseconds(int32_t value);

    const xtd::string& button_ok_text() const;
    message_notifier& button_ok_text(const xtd::string& value);
    message_notifier& button_ok_text(std::nullptr_t); // Reset to default value

    xtd::forms::notifier_result notifier_result() const;

    /// ...

    xtd::forms::message_notifier_buttons buttons() const;
    message_notifier& buttons(xtd::forms::message_notifier_buttons value);

    const xtd::drawing::image& icon() const;
    message_notifier& icon(xtd::forms::message_notifier_icon value);
    message_notifier& icon(const xtd::drawing::image&);
    message_notifier& icon(const xtd::drawing::icon&);

    /// Events
    event<message_notifier, xtd::forms::notifier_closed_event_handler> notifier_closed;

  protected:
    virtual void on_notifier_closed(const xtd::forms::notifier_closed_event_args& e);

    // ...     
  };
}

Example of message_notifier usage :

message_notifier notfier;
notifier.title("Title");
notifier.message("message...");
notifier.notifier_icon();
notifier.icon(message_notifier_icon::question); // or: notifier.icon(xtd::drawing::image("my_icon"));
notifier.buttons(message_notifier::buttons::ok_cancel);
notifier.cancel_button_text("Dismiss");
notifier.notifier_closed += [&](const notifier_closed_event_args& e) {
  if (e.notifier_result() == notifier_result::ok) // or: if (notifier.notifier_result() == notifier_result::ok)
   //...
};
notifier.show();
gammasoft71 commented 2 years ago

After some thought and research, the following code shows the new implementation proposal:

namespace xtd {
  namespace forms {
    class notifier_button : public xtd::forms::component {
      public:
        notifier_button() = default;
        notifier_button(const xtd::ustring& text);

        const xtd::ustring& text() const noexcept;
        notifier_button& text(const xtd::ustring& value);
    };

    using notifier_button_ref = std::reference_wrapper<notifier_button>;

    class notifier_button_click_event_args : public xtd::event_args {
    public:
      //...

      xtd::forms::notifier_button button() const;

    //...
    };

    class notifier_closed_event_args : public xtd::event_args {
    public:
      //...

      std::optional<xtd::forms::notifier_button> button() const;
      bool close_on_timeout() const noexcept;
      bool close_on_click_message() const noexcept;

      //...
    };

    class message_notifier : public xtd::forms::componant {
    public:
      /// ...

      /// @name Alias

      using notifier_button_collection = xtd::forms::layout::arranged_element_collection<notifier_button_ref>;

      /// Properties

      const notifier_button_collection& buttons() const noexcept;
      notifier_button_collection& buttons();

      bool close_timeout_enabled() const noexcept;
      message_notifier& close_timeout_enabled(bool value);

      std::chrono::milliseconds close_timeout_interval() const noexcept;
      message_notifier& close_timeout_interval(std::chrono::milliseconds value);
      message_notifier& close_timeout_interval_milliseconds(int32_t value);

      std::optional<xtd::forms::notifier_button> notifier_button_clicked() const;

      /// ...

      const xtd::drawing::image& icon() const;
      message_notifier& icon(const xtd::drawing::image&);
      message_notifier& icon(const xtd::drawing::icon&);

      /// Events

      event<message_notifier, xtd::forms::notifier_button_click_event_handler> button_click;
      event<message_notifier, xtd::forms::notifier_closed_event_handler> notifier_closed;

    protected:
      virtual void on_button_click(const xtd::forms::message_notifier_button_click_event_args& e);
      virtual void on_notifier_closed(const xtd::forms::notifier_closed_event_args& e);

      // ...     
    };
  }
}

And usage :

#include <xtd/xtd>

using namespace xtd;
using namespace std::literals;
using namespace xtd::forms;

namespace examples {
  class form1 : public form {
  public:
    form1() {
      text("Message notifier example");

      button1.parent(*this);
      button1.location({10, 10});
      button1.text("Notify");
      button1.click += [&] {
        message_notifier1.show();
      };

      message_notifier1.buttons().push_back_range({start_message_notifier_button, cancel_message_notifier_button});
      message_notifier1.close_timeout_enabled(true);
      message_notifier1.close_timeout_interval(2s);
      message_notifier1.icon(xtd::drawing::system_icons::question());
      message_notifier1.message("Start the auto backup now");
      message_notifier1.notifier_style(notifier_style::standard);
      message_notifier1.title("Backup");
      message_notifier.button_click += {*this, &form1::on_message_notifier_button_click};
    }

  private:
    void on_message_notifier_button_click(object& sender, const on_message_notifier_button_click_event_args& e) {
      if (e.button() == start_message_notifier_button)
        diagnostics::debug::write_line("Start backup");
      else
        diagnostics::debug::write_line("Cancel backup");
    }

    button button1;
    message_notifier_button start_message_notifier_button {"&Start"};
    message_notifier_button cancel_message_notifier_button {"&Cancel"};
    message_notifier message_notifier1;
  };
}

int main() {
  application::run(examples::form1());
}