Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

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

Checkbox checkmark is not updated when linking controls. #179

Closed
wsberry opened this issue Jul 9, 2020 · 20 comments
Closed

Checkbox checkmark is not updated when linking controls. #179

wsberry opened this issue Jul 9, 2020 · 20 comments
Assignees

Comments

@wsberry
Copy link

wsberry commented Jul 9, 2020

I may be using the control incorrectly.

The workflow is such that when a checkbox is clicked then I would like other checkboxes to also be updated.
i.e. Click checkbox A and have checkbox B reflect the same checked state as A.

Example Code:

#pragma once

#include <elements.hpp>
#include <memory_resource>

namespace experimental::elements
{
namespace colors_rgba
{
constexpr auto alpha = 200;
constexpr auto default_dark = cycfi::elements::rgba(32, 32, 32, alpha);
constexpr auto slate_gray = cycfi::elements::rgba(112, 128, 144, alpha);
constexpr auto antique_white = cycfi::elements::rgba(250, 235, 215, alpha);
constexpr auto doger_blue = cycfi::elements::rgba(16, 78, 139, alpha);
}

namespace colors_rgb
{
constexpr auto default_dark = cycfi::elements::rgb(32, 32, 32);
constexpr auto slate_gray = cycfi::elements::rgb(112, 128, 144);
constexpr auto antique_white = cycfi::elements::rgb(250, 235, 215);
constexpr auto doger_blue = cycfi::elements::rgb(16, 78, 139);
}

using check_box_t = cycfi::elements::basic_toggle_button<cycfi::elements::proxy<cycfi::elements::check_box_element, cycfi::elements::basic_button>>;

namespace grpc::controller
{
// TODO: Load from config file.
//
auto constexpr default_path = "";

  // Main window background color
  // Is it possible to effect forground colors_rgba?
  //
  auto constexpr bkd_color = elements::colors_rgba::slate_gray;

  class test_options_dialog final
  {
     const cycfi::elements::box_element background = cycfi::elements::box(bkd_color);

     elements::check_box_t check_box1 = cycfi::elements::check_box("gRPC Simulations Server");
     elements::check_box_t check_box2 = cycfi::elements::check_box("gRPC GPS Simulator");
     elements::check_box_t check_box3 = cycfi::elements::check_box("gRPC UAV Simulator");

     // This works, but the repainting of check_box1, 2, and 3 does not.
     // Therefore the user does not receive the desired feedback.
     //
     elements::check_box_t check_box_select_all = cycfi::elements::check_box("Select All");

     std::size_t uav_instances_to_create_ = 5;

     auto make_selection_menu()
     {
        return cycfi::elements::selection_menu(
           [&](std::string_view& select)
           {
              const char* count = select.data();
              uav_instances_to_create_ = std::atol(count);
           },
  {
     "5",
     "10",
     "15",
     "20",
  }

  ).first; // We return only the first, the menu. the second is a shared pointer to the label.
     }

     void run_selected_options() const
     {
        using namespace cycfi::elements;

        const auto simulations_data_bus = std::string(default_path).append("simulations_data_bus.exe");
        const auto gps_device_emulator = std::string(default_path).append("gps_device_emulator.exe");
        const auto uav_entity_simulator = std::string(default_path).append("uav_emulator.exe");

        std::string args = "-ignore";

        if (check_box1.value())
        {
           //system::process::start_process(simulations_data_bus.c_str(), args);
        }

        if (check_box2.value())
        {
           //system::process::start_process(gps_device_emulator.c_str(), args);
        }

        if (check_box3.value())
        {
           auto* uav_fmt = " -uav:\"UAV ";
           const auto end = uav_instances_to_create_;
           for (auto n = 0; n < end; ++n)
           {
              auto arg = uav_fmt + std::to_string(n).append("\"");
              //system::process::start_process(uav_entity_simulator.c_str(), arg);
           }
        }
     }

     auto options_button_layout(cycfi::elements::view& window_view)
     {
        using namespace cycfi::elements;

        auto run_options_button = button("Run Selected Options");

        run_options_button.on_click =
           [&](bool) mutable
        {
           run_selected_options();
        };

        return
           margin({ 20, 0, 20, 20 },
              vtile(
                 top_margin(20, run_options_button),
                 top_margin(10,

                    htile(
                       margin({ 0, 0, 10, 0 },
                          label("UAV Count:")
                          .text_align(canvas::right)),
                       hmin_size(30, make_selection_menu()))
                 ),
                 top_margin(10, check_box_select_all)
              )
           );
     }

     void assign_checkbox_event_handlers(cycfi::elements::view& window_view)
     {
        check_box_select_all.on_click =
           [&](bool) mutable
           {
              check_box_select_all.value(!check_box_select_all.value());

              // ' window_view.refresh(check_box1);' code is not working...
              //
              const auto update = check_box_select_all.value();

              check_box1.on_click(update);
              check_box2.on_click(update);
              check_box3.on_click(update);

              return true;
           };

        check_box1.on_click =
           [&](bool) mutable
           {
              check_box1.value(!check_box1.value());
              window_view.refresh(check_box1);
           };

        check_box2.on_click =
           [&](bool) mutable
           {
              check_box2.value(!check_box2.value());
              window_view.refresh(check_box2);
           };

        check_box3.on_click =
           [&](bool) mutable
           {
              check_box3.value(!check_box3.value());
              window_view.refresh(check_box3);
           };
     }

     auto create_view_models(cycfi::elements::view& window_view)
     {
        using namespace cycfi::elements;

        check_box1.value(false);
        check_box2.value(false);
        check_box3.value(false);

        assign_checkbox_event_handlers(window_view);

        auto  check_boxes =
           group("Components",
              margin({ 10, 10, 20, 20 },
                 top_margin(25,
                    vtile(
                       top_margin(10, align_left(check_box1)),
                       top_margin(10, align_left(check_box2)),
                       top_margin(10, align_left(check_box3))
                    )
                 )
              )
           );

        return
           vtile(
              htile(
                 options_button_layout(window_view),
                 vtile(
                    margin({ 20, 20, 20, 20 }, check_boxes)
                 )
              )
           );
     }

  public:

     auto show(const int argc = 0, char* argv[] = nullptr)
     {
        using namespace cycfi::elements;

        app app_instance(argc, argv, "gRPC - Simulations (Munitions) Data Bus", "");
        window window_model(app_instance.name());
        window_model.on_close = [&app_instance]() { app_instance.stop(); };
        view window_view(window_model);
        window_view.content(create_view_models(window_view), background);
        app_instance.run();
     }
  };
  inline int show_dialog(const int argc, char* argv[])
  {
     test_options_dialog dialog;
     dialog.show(argc, argv);
     return 0;
  }

}
} // using namespace experimental::elements;

//#include "grpc-startup-controller.hh"

using namespace experimental::elements::grpc;

/**

  • \brief
  • \param argc
  • \param argv
  • \return
    /
    int main(const int argc, char
    argv[])
    {
    return controller::show_dialog(argc, argv);
    }

Again, this is awesome work you are doing. Thanks so so much!

  • Bill Berry
@djowel
Copy link
Member

djowel commented Jul 9, 2020

I suggest either 1) placing the controls in a composite (e.g. a pane or something) and refreshing the whole composite window_view.refresh(a_pane) or 2) simply refreshing the whole view window_view.refresh().

@wsberry
Copy link
Author

wsberry commented Jul 10, 2020 via email

@djowel
Copy link
Member

djowel commented Jul 10, 2020

Feel free to ask more questions, or even suggest improvements.

@djowel
Copy link
Member

djowel commented Jul 14, 2020

@wsberry were you able to solve your issue? Is it good to close this now?

@wsberry
Copy link
Author

wsberry commented Jul 15, 2020 via email

@wsberry
Copy link
Author

wsberry commented Jul 15, 2020 via email

@djowel
Copy link
Member

djowel commented Jul 16, 2020

Joel, The following did not work:

Hrmmm... Well, if you post an MVCE (as minimal as you can get it to be), I can try it out myself to see what's happening. The view refresh mechanism should work. The radio buttons and menus do something similar to what you are doing.

@djowel
Copy link
Member

djowel commented Aug 26, 2020

@wsberry is this still an issue and will you post an MVCE if it is?

@djowel djowel self-assigned this Aug 26, 2020
@wsberry
Copy link
Author

wsberry commented Aug 26, 2020 via email

@djowel
Copy link
Member

djowel commented Aug 26, 2020

Appreciated!

@djowel
Copy link
Member

djowel commented Sep 13, 2020

Any update?

@wsberry
Copy link
Author

wsberry commented Sep 14, 2020 via email

@djowel
Copy link
Member

djowel commented Sep 15, 2020

OK, got it. Thanks for the MVCE. It really helps!

The main issue here is that: if you want an element to interact with other elements, you need to "share" it and "hold" the shared element in the composition. There are a couple of unrelated changes I made to your code which I shall explain below. Here's the correct code:

#include <string>
#include <iostream>
#include <elements.hpp>

using namespace cycfi::elements;

namespace colors
{
    constexpr auto default_dark = rgba(32, 32, 32, 255);
    auto constexpr bkd_color = ::colors::default_dark;
}

// Basic check box type
//
using check_box_t = decltype(share(check_box("name")));

class test_options_dialog final
{
    const box_element background =      box(::colors::bkd_color);
    check_box_t check_box1 =            share(check_box("A"));
    check_box_t check_box2 =            share(check_box("B"));
    check_box_t check_box3 =            share(check_box("C"));
    check_box_t check_box_select_all =  share(check_box("Select All"));

    void run_selected_options() const
    {
        std::string args = "-ignore";

        if (check_box1->value())
        {
            std::cout << "1 selected\n";
        }
        else
        {
            std::cout << "1 not selected\n";
        }

        if (check_box2->value())
        {
            std::cout << "2 selected\n";
        }
        else
        {
            std::cout << "2 not selected\n";
        }

        if (check_box3->value())
        {
            std::cout << "3 selected\n";
        }
        else
        {
            std::cout << "3 not selected\n";
        }
    }

    auto options_button_layout(view& window_view)
    {
       auto run_options_button = button("Run Selected Options");

       run_options_button.on_click =
            [&](bool) mutable
            {
                run_selected_options();
            };

       return
            margin({ 20, 0, 20, 20 },
                vtile(
                    top_margin(20, run_options_button),
                    top_margin(10, hold(check_box_select_all))
                )
            );
    }

    auto create_view_models(view& window_view)
    {
       check_box1->value(false);
       check_box2->value(false);
       check_box3->value(false);

       check_box_select_all->on_click =
            [&](bool) mutable
            {
                const auto update = check_box_select_all->value();

                check_box1->value(update);
                check_box2->value(update);
                check_box3->value(update);

                window_view.refresh();

                return true;
            };

       auto  check_boxes =
            group("Components",
                margin({ 10, 10, 20, 20 },
                    top_margin(25,
                        vtile(
                            top_margin(10, align_left(hold(check_box1))),
                            top_margin(10, align_left(hold(check_box2))),
                            top_margin(10, align_left(hold(check_box3)))
                        )
                    )
                )
            );

       return
            vtile(
                htile(
                    options_button_layout(window_view),
                    vtile(
                        margin({ 20, 20, 20, 20 }, check_boxes)
                    )
                )
            );
    }

public:

    auto show(const int argc = 0, char* argv[] = nullptr)
    {
       app app_instance(argc, argv, "Checkbox Tester", "anyar.research.experimental");
       window window_model(app_instance.name());
       window_model.on_close = [&app_instance]() { app_instance.stop(); };
       view window_view(window_model);

       window_view.content(
            create_view_models(window_view),
            background
       );

       app_instance.run();
    }
};

inline int show_dialog(const int argc, char* argv[])
{
    test_options_dialog dialog;
    dialog.show(argc, argv);
    return 0;
}

int main(const int argc, char* argv[])
{
    return show_dialog(argc, argv);
}

A couple of things:

  1. Use decltype. It will make your life so much easier. See line 15.
  2. The interacting check-boxes are "shared".
  3. The interacting elements are held ("hold"). Lines 73 and 108-110.
  4. Don't set the value via on_click. Do it via btn->value(val); line 94-96

Aside: I removed the code where you changed the value of the button in the on_click (e.g. check_box3.value(!check_box3.value());). That is not necessary and is incorrect.
Another aside: A non-opaque background might be confusing (in this case) because the previous drawing state persists.

I hope this helps! This will be clarified in the documentation currently WIP.

@djowel
Copy link
Member

djowel commented Sep 15, 2020

The reason why you need to "share" and "hold" interacting elements is that the elements uses value semantics everywhere. So, when you make a composition, the elements are actually copied. Therefore, if you do not share the element, it will not be the same element that is actually held in the composition. "share" and "hold" changes value semantics to reference semantics.

@djowel
Copy link
Member

djowel commented Sep 15, 2020

I'll close this now. Feel free to reopen if you are not satisfied with the answer.

@djowel djowel closed this as completed Sep 15, 2020
@djowel
Copy link
Member

djowel commented Sep 15, 2020

Hmmm... actually, I'll reopen this for the sake of others who have the same problem, pending proper documentation.

@djowel djowel reopened this Sep 15, 2020
@wsberry
Copy link
Author

wsberry commented Sep 15, 2020 via email

@Xeverous
Copy link
Contributor

@djowel have you thought about different implementations of the event handling/bounds calculation? Something of ECS type or signal-slot? These are very far from elements implementation but I'm curious if you have considered them and if yes - what are your thoughts on them. IMO I have a feeling that ECS can have much more efficient handling (although every element would need to be connected to sort of cerntral registry - many elements require view anyway) and signal-slot can scale much better (instead of iterating over elemens, it's iterating over signals and connected slots).

@djowel
Copy link
Member

djowel commented Sep 15, 2020

@djowel have you thought about different implementations of the event handling/bounds calculation? Something of ECS type or signal-slot? These are very far from elements implementation but I'm curious if you have considered them and if yes - what are your thoughts on them. IMO I have a feeling that ECS can have much more efficient handling (although every element would need to be connected to sort of cerntral registry - many elements require view anyway) and signal-slot can scale much better (instead of iterating over elemens, it's iterating over signals and connected slots).

I was inclined to flag this as OT, but I didn't do it because it is tangentially related. I'll give my reply, but I'll make it short. If you want to discuss design issues further, well, this is not the place for it. There's the discord group chat for that.

Anyway, my answer will also be irrelevant primarily because at this point, it's too late in the design cycle to change things the way they are. As I pointed out elsewhere, I am no longer inclined to entertain substantial design changes.

But answering for the sake of answering... Focusing on the on_* handlers only as relevant in this discussion, I prefer direct communication between components. Signals-and-slots has its place, for sure, but in terms of design, the analogy is a bulletin board where one posts messages and interested parties connect to be notified of such posts they are interested in. Essentially a one-to-many relationship. For elements, I prefer a one-to-one relationship, because generally that's what's happening. Moreover, direct communication via the on_* handlers does not preclude one-to-many relationships. For instance, an on_click function is generic enough and can connect to an app specific signals/slots or "event bus". It's up to you, the app designer, if you want that. There is no system-wide "way of doing things" that is forced into the user. That follows the keep-it-simple design principle.

If you are thinking about replying further, please don't. Instead, move the discussion over to the discord chat group. I do not want to veer too much from the topic.

@djowel djowel mentioned this issue Sep 15, 2020
38 tasks
@Xeverous
Copy link
Contributor

Ok, thanks for the response. This answer is enough.

@djowel djowel closed this as completed Oct 21, 2023
@cycfi cycfi locked and limited conversation to collaborators Oct 21, 2023
@djowel djowel converted this issue into discussion #347 Oct 21, 2023

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Projects
None yet
Development

No branches or pull requests

3 participants